[toc]
HTTPS协议
本篇将讨论 HTTPS 的加解密原理,很多人都知道 RSA,以为 HTTPS=RSA,使用 RSA 加解密数据,实际上这是不对的。
HTTPS 是使用 RSA 进行身份验证和交换密钥,然后再使用交换的密钥进行加解密数据。
身份验证是使用 RSA 的非对称加密,而数据传输是双方使用相同的密钥进行的对称加密。那么,什么是对称加密和非对称加密?
对称加密和非对称加密
假设隔壁小王想要约小红出来,但是他不想让小明知道,于是他想用对称加密给小红传了个小纸条。
如下图所示:
他想发送的数据是"Meet at 5:00 PM"(5 点见面,如果是中文的话可以使用 UTF-8 编码),加密方式是直接在 ASCII 表进行左移或右移。
他的密钥是 3,表示在 ASCII 表往后移 3 位,就会变成"Phhw#dw#8=33#SP",这样一般人如果截获了不知道是什么意思的。
但是我们可以想一下,如果既然他可以截获你的数据,自然也可以截获你的密钥,进而进行解密。
如下图所示:
所以小王打算用非对称加密,非对称加密的特点是双方都有自己的公钥和私钥对,其中公钥发给对方,密钥不交换自己保管不泄漏。
如下图所示:
其中小红的公钥为:
public_key = (N, e) = (3233, 17)
她把公钥发给了小明,她自己的私钥为:
private_key = (N, e) = (3233, 2753)
这里注意公钥和私钥都是两个数,N 通常是一个大整数,e 表示一个幂指数。现在小王想给小红发消息,于是他用小红的公钥进行加密,怎么加密呢?
他要发送的第一个字母为 t=“M”,“M”的 ASCII 编码为 77,77 的加密过程如下计算:
T = 77 ^ e % N = 77 ^ 17 % 3233 = 3123
把 77 做 e 次幂然后模以 N,便得到了 T=3123,然后把这个数发给小红(其他字母按同样方式处理)。
小红收到 T 之后便用她的私钥进行解密,计算如下:
t = T ^ e % N = 3123 ^ 2753 % 3233 = 77
计算方法是一样的,这样便把 T 还原成了 t,只要公私钥配对,便可通过一些数学公式证明上面的推算是成立的。这个就是 RSA 的加解密原理,如果无法知道私钥便无法进行正确解密。
反过来,使用私钥进行加密,公钥进行解密也是可行的。那么 HTTPS 是怎么利用 RSA 进行加解密的呢,我们从 HTTPS 连接建立过程说起。
HTTPS 连接建立过程
HTTPS 主要有以下作用:
- 验证服务方身份,如我访问 google.com 的时候连的确实就是谷歌服务器
- 防止数据被劫持,例如有些运营商会给 http 的页面插入广告
- 防止敏感数据被窃取篡改等
正如 openssl 的注释所说,这是防止中间人攻击的唯一方法:
我们以 MDN(https://developer.mozilla.org)的网站为例,然后用 wireshark 抓包,观察 HTTPS 连接建立的过程。
如下图所示:
首先是 TCP 三次握手,然后客户端(浏览器)发起一个 HTTPS 连接建立请求,客户端先发一个 Client Hello 的包,然后服务端响应一个 Server Hello。
接着再给客户端发送它的证书,然后双方经过密钥交换,最后使用交换的密钥加行加解密数据。
在 Client Hello 里面客户端会告知服务端自己当前的一些信息,如下图所示:
包括客户端要使用的 TLS 版本,支持的加密套装,要访问的域名,给服务端生成的一个随机数(Nonce)等。
需要提前告知服务器想要访问的域名以便服务器发送相应的域名的证书过来,因为此时还没有发生 HTTP 请求。
服务端在 Server Hello 里面会做一些响应:
服务端选中的加密套装叫 TLSECDHERSAWITHAES128GCM_SHA256,这一串的意思是:
- 密钥交换使用 ECDHE
- 证书签名算法 RSA
- 数据加密使用 AES 128 GCM
- 签名校验使用 SHA256
接着服务给客户端发来了 4 个证书:
第一个证书的公用名(common name)就是我们当前访问的域名 developer.mozilla.org。
如果公用名是 *.mozilla.org 的话那么这个证书便能给 mozilla.org 的所有二级子域名使用。
第二个证书是第一个证书的签发机构(CA)的证书,它是 Amazon,也就是说 Amazon 会用它的私钥给 developer.mozilla.org 进行签名。
依此类推,第三个证书会给第二个证书签名,第四个证书会给第三个证书签名,并且我们可以看到第四个证书是一个根(Root)证书。
一个证书里面会有什么东西呢,我们可以展开第一个证书看一下,如下图所示:
证书包含三部分内容:
- tbsCertificate(to be signed certificate)待签名证书内容
- 证书签名算法
- CA 给的签名
也就是说 CA 会用它的私钥对 tbsCertificate 进行签名,并放在签名部分。为什么证书要签名呢?签名是为了验证身份。
身份验证
我们先来看一下 tbsCertificate 里面有什么内容,如下图所示:
它里面包括了证书 的公钥、证书的适用公用名、证书的有效期还有它的签发者等信息。
Amazon 的证书也具备上述结构,我们可以把 Amazon 证书的公钥拷出来,如下图所示:
中间有一些填充的数字,用灰色字表示。可以看到N通常是一个很大的整数(二进制 2048 位),而 e 通常为 65537。
然后我们用这个 CA 的公钥对 mozilla.org 的证书签名进行解密,方法和上面的类似:
取解密后的数字 decrypted 的十六进制的末 64 位,即为二进制 256 位的 SHA 哈希签名。
接下来我们手动计算一下 tbsCertificate 的 SHA256 哈希值,方法是在 wireshark 里面把 tbsCertificate 导出一个原始二进制文件:
然后再使用 openssl 计算它的哈希值,如下所示:
liyinchengs-MBP:https liyincheng$ openssl dgst -sha256 ~/tbsCertificate.binSHA256(/Users/liyincheng/tbsCertificate.bin)= 5e300091593a10b944051512d39114d56909dc9a504e55cfa2e2984a883a827d
我们发现手动计算的哈希值和加密后的证书里的哈希值一致!说明只有知道了 Amazon 私钥的人才能正确地对 mozilla.org 的证书签名,因为公私钥是唯一匹配的。
因此我们验证了第一个证书 mozilla.org 确实是由第二个证书 Amazon 签发的,使用同样的方式,我们可以验证 Amazon 是由第三个签发的,第三个是由第四个根证书签发。
并且第四个证书 是根证书,它是内置于操作系统的(通过 Mac 的 keychain 工具可以查看):
假如 Hacker 通过 DNS 欺骗之类的方式把你访问的域名指向了他的机器,然后他再伪造一个证书。
但是由于根证书都是内置于操作系统的,所以它改不了签名的公钥,并且它没有正确的私钥,只能用自己的私钥,由于公私钥不配对,很难保证加解密后的信息一致。
或者直接把浏览器拿到的证书搬到他自己的服务器?这样再给浏览器发的证书便是一模一样,但是由于他不知道证书的私钥,所以无法进行后续的操作,因此这样是没有意义的。
这个就是 HTTPS 能够验证身份的原理。另外一个例子是 SSH,需要手动验证签名是否正确。
例如通过打电话或者发邮件等方式告知服务器的签名,与自己算的证书的签名是否一致,如果一致说明证书没有被篡改过(如证书的公钥没有被改为 Hacker 的公钥):
上面展示的便是自己手动计算的值,拿这个值和之前的值进行比较是否相等便可知发过来的证书是否被修改过。
那么,为什么不直接使用 RSA 的密钥对进行加密数据?因为 RSA 的密钥对数值太大,不太合适频繁地加解密数据,所以需要更小的密钥。
另一个原因是服务端没有浏览器或者客户端的密钥,无法向浏览器发送加密的数据(不能用自己的私钥加密,因为公钥是公开的)。所以需要进行密钥交换。