HTTPS 详解

HTTP 安全隐患

在 https 诞生之前,网站一般与用户使用 http 通信,例如以下消息:

code shell
curl http://localhost/send -X POST -H "Authorization: xxxxxx" -d 'content=阿珍,我是阿强'

通过 wireshark 抓包可以看到,用户的鉴权信息和消息内容均以明文呈现:

这就为攻击者提供了可乘之机,总的来说有两种攻击方式:

中间人攻击

随着互联网规模的日益增长,用户数据从客户端到服务端可能需要经过多个代理服务器、路由器等中间节点,这些节点只要有一个被攻破就可以拿到或篡改用户数据。

中间人攻击产生的主要原因一是数据未加密造成信息泄露,二是接收端无法验证数据为原始信息。

DNS 劫持

DNS 全称域名系统(Domain Name System),它维护了一个域名映射至 IP 地址的数据库,以方便使用者快速到达指定服务器。这就意味着如果攻击者劫持了 DNS 就可以将某个域名指向另外的 IP 地址,用户发送的请求就会被转发到攻击者的服务上,从而造成信息泄露。

出于对加速访问和测试等特殊场景的考虑,操作系统一般还会提供一个本地的 Hosts 文件,该文件中同样维护了域名到 IP 的映射,Hosts 文件的优先级要高于 DNS 服务,如果在 Hosts 文件中找到了请求域名,则不再请求 DNS 服务器。 例如上图中使用的 localhost 一般在 Hosts 文件中被映射到 127.0.0.1,攻击者通过控制 Hosts 文件也可以达到 DNS 劫持的目的。

DNS 劫持除了直接修改用户侧的 DNS 服务器地址,还可以使用 DNS 服务的中间人攻击直接篡改映射结果。因为 DNS 服务默认也是通过明文进行传输,wireshak 可以证明这一点:

查询 DNS shell
# 访问 8.8.8.8 查询 dns 结果
dig @8.8.8.8 baidu.com

为了解决这个问题,诞生了 DNS over HTTPS,以 HTTPS 协议完成 DNS 解析来保护网络主机的隐私。具体内容不在本文叙述范围内,可自行查阅 维基百科 DNS over HTTPS

*注:目前最新版 Edge 浏览器默认开启了 DNS over HTTPS,但国内一些路由器厂商未及时跟进,导致用户无法正常上网(对没错,就是某米)。解决方案要么修改 PC 端 DNS 地址,要么关闭 Edge 的 DNS over HTTPS。

通过上面的介绍可以发现 DNS 被劫持最主要的原因是用户无法知晓域名和 IP 地址的映射关系是否合法。

HTTPS 的诞生

回到最原始的需求,A 和 B 通过中继间接通信,如何保证通信内容只有 AB 知晓。我们很容易想到将消息内容加密,例如将明文做映射替换,假设有如下字典:

字典 text
阿 => 0
珍 => 2
,=> 3
我 => 9
是 => 7
阿 => 6
强 => 5

那么 A 发送给 B 的信息为: 0239765,B 接收到消息后,按找字典反向映射即可得到原始信息: 阿珍,我是阿强。这样即使中继节点拿到了消息没有字典也翻译不出原始信息,我们将这样的字典称之为密钥

但是 A 如何将密钥告知 B 呢?如果 A 将密钥以明文的形式告知 B,中继节点就又有机会拿到密钥破解后续的加密消息了。

像这样加密方和解密方使用同一个密钥的加密形式称之为对称加密,显然对称加密并不能解决中间人攻击的问题。

为了解决这个问题,AB换了一种方式通信,如果 A 想给 B 发送消息,需要先发送一条明文消息,索要一个带锁但并未上锁的盒子,该盒子锁的钥匙只有 B 才有,A 拿到盒子以后把消息放到盒子中并上锁,再发送给 B,这样中间节点即使拿到了盒子,也无法打开。

这种加密方式被称为非对称加密,即消息加密和解密使用两个不同的密钥,这两个密钥呈数学相关性,并且公开其中一个密钥并不会影响另一个密钥的秘密性质。我们通常将公开的密钥称为公钥,不公开的密钥称为私钥,用公钥加密后的数据只能用私钥解开,用私钥加密的数据只能用公钥解开。结合上图,带锁的盒子其实就是公钥,B 持有的钥匙就为私钥。

上面的流程可以保证 A 发送的消息只能被 B 看到,但是如果 B 要给 A 回复消息,又该怎么做呢?既然公钥私钥可以互相解密,那我用私钥加密消息给 A 不就行了吗。仔细想想这样真的可行吗?

注意到 B 给 A 发送公钥时使用的是明文,这就导致中间人可以截取到公钥,从而解密 B 给 A 发送的消息,同时也可以通过公钥篡改消息。

解决这个问题其实也很简单,既然一组公钥私钥只能保护一个方向的安全加密,那就引入两组密钥,A 和 B 分别持有对方的公钥,即可保证双向的安全加密。

这样中间人即使截取了公钥和消息,也无法成功解密了。这种方式虽然一定程度上解决了中间人攻击问题,但 HTTPS 并未直接采用双组密钥的体系,主要原因是性能问题,相较于对称加密算法非对称加密非常耗时。所以 HTTPS 最终采用了非对称加密 + 对称加密的方式,流程如下:

这样既提高了传输的安全性,也照顾到了性能问题。

截止到目前,我们已经解决了 HTTP 协议中数据安全加密的问题,但通信双方仍旧无法验证对方的身份(例如 DNS 被劫持),即消息接收方无法验证自己得到的公钥是正确的,攻击者可以伪造对方身份在中间做代理截取信息:

为了避免这种情况出现,诞生了数字证书的概念,数字证书相当于密钥的“身份证”,“身份证”号码为网站的域名。如同人类社会一样,身份证由政府颁发,并由政府负责其真实性,数字证书由 CA 机构颁发。CA 机构相当于互联网世界中的政府,具有绝对的公信力。CA 机构本身也有一组自己的非对称密钥,公钥部分会提供给操作系统或浏览器等厂商进行内置,用于对数字证书签名的校验。

数字证书由两大部分组成:

  • 证书信息
    证书生成之前,CA 机构会先给域名生成一组非对称密钥。其中私钥后连同数字证书一并发给申请者。
    证书信息中则包括刚刚生成的公钥(客户端与服务端通信的公钥)、签发者 ID(会在验证签名时用到)、域名信息(相当于证书的身份证号)、有效期等信息。

  • 内容摘要签名
    为了保证证书信息不被篡改,需要为证书添加签名。首先对证书信息进行数据摘要,例如使用 Hash 算法得到证书信息的 Hash 值 H,将 H 用 CA 机构自己的私钥加密得到内容摘要签名 S

将证书信息和内容摘要签名组合到一起就得到了完整的数字证书,网站负责人则需要将数字证书和私钥部署到自己的服务上。

客户端进行 HTTPS 访问时,服务端会将数字证书返回,浏览器端则会根据数字证书中的签发者 ID 找到对应的 CA 公钥,使用 CA 公钥解密数字证书中的内容摘要签名 S 得到 H,然后使用相同的摘要算法计算证书信息部分的摘要值 H',如果 HH' 一致,则说明证书未被篡改。

以浏览器为例,如果证书的颁发机构不被被浏览器厂商信任,则会以一定的手段告知用户存在安全隐患,但用户仍可继续访问。

世界上的 CA 机构也不是一成不变的,比如新增了一个机构,就意味着所有的网络终端都需要新安装一个证书,而事实上我们好像很少遇到需要手动安装 CA 证书的情况。原来是因为数字证书本身是支持链式结构的,数字证书本身是用来证明一个公钥的合法性,而数字证书本身也包含一个公钥,所以我们可以使用一个证书来证明另外一个证书,比如 A 信任 B,B 信任 C,那么 A 也信任 C。其中 A 称为根证书,一般浏览器内置的均为根证书。

至此,HTTPS 整体思路已经介绍完毕,下一章节则会着重介绍 HTTPS 协议的实现原理。

HTTPS 的实现原理

从协议栈角度看 HTTPS 与 HTTP 的区别并不大:

可以看到,HTTPS 就是 在 HTTP 之前引入了一个安全层。TLS(Transport Layer Security,传输层安全性协议)是 SSL(Secure Sockets Layer,安全套接层)的继承者。SSL 最早由网景公司(Netscape)在 1994 年推出,IETF 后将 SSL 进行标准化 并于 1999 年公布 TLS 1.0 标准文件,后又经多个版本迭代,目前常用的版本为 TSL 1.2,TSL 1.3 于 2018 年发布,但由于历史包袱沉重时至今日仍旧没有大范围支持。虽然目前主流使用均为 TSL,但历史上习惯了 SSL 这个称呼,所以大可不必纠结于此。

文献参考: 传输层安全性协议