互联网应用的网络通信一般都是通过 HTTP,但 HTTP 是明文传输的,容易泄漏信息,所以大多数应用都会升级为 HTTPS。
HTTP 底层是用 TCP 传输的,HTTPS 就是在 TCP 和 HTTP 之间加了一层加密和认证的协议,这一层叫做 SSL/TLS。
为什么叫这个名字呢?
因为最早的时候是 SSL 协议,但是后来发现了漏洞,就改为 TLS 协议了,而且 TLS 协议也在不断的升级,从 1.1、1.2 到了现在的 TLS 1.3。
不管叫 SSL 还是叫 TLS,都是指的这一层。
这一层就实现了加密、身份认证,还有防篡改的功能。
加密很容易理解,就是通过一种加密算法对内容进行处理,生成密文,然后另一端通过解密算法把密文处理成原始内容。
但只有加解密算法还不行,得加入一些随机性,让每次都不一样,所以会有密钥的存在,每次先随机生成密钥,然后通信过程就用这个密钥配合加密算法来进行加解密。
这个密钥是随机生成的,所以只能一端生成以后告诉另一端。
那么问题来了,怎么把这个密钥安全的告诉另一端呢?
这就得用到一种特殊的加密算法 --- 非对称加密了。
这种加密算法特殊在有两个密钥,用一个密钥加密的数据只能另一个密钥解密,那么把一个密钥暴露出去,一个密钥留下,这样用留下的密钥加密的数据,别人都能解密,但是用暴露出去的密钥加密的数据,只有自己能解密。
这个暴露出去的密钥就叫做公钥,留下的密钥叫做私钥。
这样当别人想给你传递一些信息的时候,就通过你暴露出去的公钥对信息加密,别人都解密不了,只有你能解密。这就保证了信息传递的安全性。
就比如说上面提到的密钥传递问题,就可以通过这种公私钥的非对称加密机制来解决。
有的同学可能会问,既然基于公私钥的非对称加密机制能保证安全,为啥还要再传递别的密钥呢?
因为这种非对称加密太慢了,传递几次信息还行,频繁的用这种方式加解密数据的话效率太低了。所以一般只用这种方式来传递会话密钥,保证安全,然后后面用传递的会话密钥来进行数据加解密。
前面提到 TLS 层主要是实现了加密、身份认证、防篡改的功能。
加密是用对称加密的方式,用到的密钥通过基于公私钥的非对称加密机制来传递。
那身份认证怎么做呢?
其实也是通过公私钥的机制,刚才提到了公钥加密的内容只能私钥解密,这保证了信息的安全传递。
那反过来,私钥加密的数据,如果用公钥能解开,这不就证明了信息是你传递的么?因为私钥只有你有。
所以,私钥的加密又叫做签名,可以用来做身份的认证。
那用私钥加密什么呢?
一般是对传输的信息做一次 hash,生成数据指纹,然后用私钥加密这个数据指纹,也就是对它进行签名。
这样数据传递到另一端,用公钥把数据指纹取出来,再对内容做一次 hash,生成一份数据指纹,两者对比一下,如果一样,就说明没有被篡改。
这就是 TLS 层的第三个功能,防篡改,也就是保证数据的完整性。
至此,HTTPS 给 HTTP 额外添加的加密、身份认证、防篡改功能的实现原理我们都了解了。
但不知道同学们有没有发现这其中有个漏洞,非对称加密的算法是公开的,你可以生成公私钥,别人也可以,那怎么保证我拿到的公钥是你的呢?
万一我拿到的公钥是别人的,那我用它加密的数据,不就被别人截去了么?
想解决这个问题就涉及到一个新的概念,数字证书了:
数字证书
现在的问题是怎么验证公钥是某个人的。
如果我有信得过的人,他说这个公钥是那个人的,我就相信。基于这样的信任来验证可以么?
也就是说我信任的人有自己的公私钥,他用私钥对这段信息签名,我收到信息后用他的公钥来解密,发现能解密出其中的信息,说明这是被他签名过的,我就相信我收到的公钥是可靠的。
这样是可以的,但怎么保证我收到的信任的人的公钥是真的呢?
这就无限循环起来了。
现实中肯定不会这样无限循环的,解决方式是操作系统内置了一批信得过的机构的公钥,经过这些机构签名的,就一定是对方的公钥。
这种信得过的机构叫做 CA(Certification Authority),电子认证机构,经过 CA 认证的公钥和相关信息,就叫做数字证书。
操作系统内置了所有可信的 CA 的证书,也就是 CA 的公钥和相关信息,叫做根证书。
在 mac 下可以在钥匙串里看到系统内置的所有根证书:
当你打开一个 https 的网站的时候,会把网站的证书下载下来,看看是不是经过这些系统内置过的 CA 根证书所信任的证书,如果是,代表收到的网站的公钥是可信的。
证书都是颁给某个域名的,也就是证明证书里的公钥是这个域名的。
比如打开 https://baidu.com ,查看它的证书:
你会看到这样的三级证书,根证书是系统内置的 CA 的证书,它信任了一个中间的证书,然后这个中间证书信任了 baidu.com 的证书。(信任就是指用自己的私钥做了签名)
这个 CA颁发的根证书是内置在系统里的,受信任的,所以也就也就信任了他信任的中间证书,从而信任了中间证书信任的 baidu.com 的证书,这是一条信任链。
你打开 taobao.com 也会看到是这样的三级证书链:
为什么都是三级呢?
因为这样万一中级证书不能信任了,还可以让根证书再找一个中级证书,因为信任根证书,也自然信任这个新的中级证书,但如果根证书直接信任某个网站的证书,万一根证书被攻破不能信任了,那就找不到可以信任的了。
所以,三级证书会更安全一些。
也就是这样的一条信任链:
也就是说想给网站升级 HTTPS,得找 CA 申请个证书才行。
一般的云计算平台都提供了代理申请的服务,不过一年的费用挺贵的,比如阿里云:
如果我只是想测试下 HTTPS,要花这么大成本去 CA 申请个证书么?
那倒不用,我们可以自己创建一个 CA 根证书,然后用它给自己颁发证书,这叫自签名证书:
自签名证书
当测试的时候,可以用 openssl 这个库自己创建一个 CA 根证书。
第一步用 genrsa 命令生成私钥:
代码语言:javascript复制openssl genrsa -out ca-key.pem -des 1024
过程中要输入口令,这个是保护私钥用的。
然后用 req 命令创建证书签名请求,输入域名和相关信息:
代码语言:javascript复制openssl req -new -key ca-key.pem -out ca-csr.pem
这个过程中要输入一些信息,最重要的是域名信息,因为证书就是为了证明公钥是这个域名的:
然后用 x509 命令生成根证书:
代码语言:javascript复制openssl x509 -req -in ca-csr.pem -signkey ca-key.pem -out ca-cert.pem
至此,根证书创建完了,产生了 ca-key.pem、ca-csr.pem、ca-cert.pem 三个文件,分别是私钥、证书签名请求、根证书。
然后用这个根证书创建网站的证书。
也是同样的三步:
用 genrsa 生成私钥:
代码语言:javascript复制openssl genrsa -out server-key.pem 1024
然后用 req 命令创建证书签名请求。
代码语言:javascript复制openssl req -new -key server-key.pem -out server-csr.pem
最重要的也是域名信息:
最后一步生成证书,但是这里要指定前面生成的 QA 的根证书:
代码语言:javascript复制openssl x509 -req -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -in server-csr.pem -out server-cert.pem -extensions v3_req
这样就产生了 server-key.pem、server-csr.pem、server-cert.pem 三个文件,分别是网站的私钥、证书签名请求、证书。
用私钥和证书就可以创建 https 服务了,我们使用 nodejs 来创建:
代码语言:javascript复制const https = require('https');
const fs = require('fs');
let options = {
key: fs.readFileSync('./server-key.pem'),
cert: fs.readFileSync('./server-cert.pem')
};
https.createServer(options,function(req,res){
res.write('hello https');
res.end();
}).listen(3000);
然后浏览器访问,就可以看到 https 服务返回的内容:
chrome 会标记为不安全,我们点开看一下证书:
会看到只有一级 www.ggg.com 的证书,还没有被信任。
这是因为签发他的根证书没有导入钥匙串,我们导入一下:
导入 ca-cert.pem,就可以在钥匙串中找到 guangguangguang.com 的根证书,已经标记了是自签名证书:
再访问网站,就会看到二级的结构了:
但是还没有被信任,我们信任一下自签名的根证书:
再去网站看一下,就可以看到证书受信任了,因为颁发他的根证书受信任了:
不过网站依然会标记为不安全,这是 chrome 的策略,不支持自签名证书。
一般我们还是会向 CA 机构申请一个证书的,但是测试的时候可以自行创建一个 CA 根证书,然后给自己颁发网站的证书。
总结
HTTPS 就是在 TCP 和 HTTP 之间加了一个 SSL 或者叫 TLS 层,实现了加密、身份认证、防篡改的功能。
为了增加随机性,每次都要生成密钥来做加解密,传输这个密钥需要用到非对称加密的公私钥机制。
公钥加密的内容只有私钥能解开,防止被窃取。
私钥只有一个人有,所以加密的内容可以用作身份认证,也就是签名。
对内容做 hash,然后私钥签名,就能做到完整性校验,防止被篡改。
但是如何保证拿到的公钥一定是对方的,这是个复杂的问题。
现在的方案是系统内置了一些 CA 的根证书,然后这些 CA 证书颁发了一些网站的证书,如果访问网站拿到的证书是这些 CA 机构颁发的,那就是受信任的。
不过现实中一般都是三级的证书信任链,增加安全性。
向 CA 申请证书可以用阿里云之类的云计算提供商的代理服务,但都挺贵的,如果测试的话,可以用 openssl 自己创建一个 CA 根证书,自己给自己签名,这叫做自签名证书。
理解了这条证书信任链,也就理解了 HTTPS 的核心。