最近这几天互联网似乎不太平,先是证书私钥泄露,后是包括 PHP在内的多种语言爆出漏洞。趁着这次私钥泄露事件,复习一下专业课的相关知识,再了解一下 HTTPS 背后的密码学原理。

私钥泄露

根据公开记录,10月17日,Mozilla 收到报告称,eMudhra 的 8 张中间 CA 证书对应的私钥已失守。随后,Mozilla 确认已有多人独立恢复出了这些私钥,说明相关密钥的生成过程存在严重问题。

部署网站时,经常需要配置 HTTPS 证书和私钥。但究竟什么是证书,什么又是公私钥?为什么 CA 自己的私钥出了问题会影响到其他网站?这篇文章就从这些问题开始,把 HTTPS 背后的过程捋一遍。

公钥和私钥

先从对称加密开始,加密密钥和解密密钥是同一个,这就是对称加密,常见算法有 AES。它的效率高,适合加密大量数据。但有一个问题:两个人一开始怎么安全地拿到同一个密钥?万一密钥被别人截获,岂不是可以直接解密消息?

非对称加密是另一种思路,加解密可以用不同的密钥。基于数学原理生成一对密钥,公钥可以公开,私钥自己保管。在算法、参数和密钥生成过程安全的前提下,可以从私钥计算出公钥,但根据公钥反推私钥,在现实计算资源下不可行。常见算法有 RSA。

举个例子,小明向小红发送信息,小明用小红的公钥加密信息,然后小红再用自己的私钥解密。这样,小红只需要公开公钥。不过,小明怎么知道拿到的公钥真的是小红的?如果中间人把它换成自己的公钥,消息确实加密了,却加密给了别人。这个问题怎么解决?

数字证书和 CA

为了解决公钥被冒充的问题,需要有人为“这个公钥属于谁”作证明,这就引出了数字证书和 CA。

CA,全称 Certificate Authority,证书颁发机构。以网站常见的域名验证型证书为例,申请者需要先证明自己能控制这个域名,比如按要求设置一条 DNS 记录。验证通过后,CA 才会签发证书,一张网站证书通常包含这些信息:

  • 适用的域名。
  • 网站的公钥。
  • 签发者、有效期及用途。
  • 签发者的数字签名。

不过,证书也是一份数据,攻击者为什么不能修改里面的公钥,或者自己编一张证书?这就要说到数字签名。

数字签名

加密是为了让别人看不懂消息,签名则是为了验证消息的来源,以及内容有没有被修改。

具体原理如下:CA 使用自己的私钥对证书内容(比如上面提到的网站证书)签名,浏览器用 CA 的公钥进行验证,如果攻击者修改了证书里的域名或网站的公钥,原来的签名就无法通过验证。如果攻击者自己编一张证书,没有对应的 CA 私钥,生成的签名也无法被 CA 公钥验证。

所以网站公钥由 CA 证明,那 CA 的公钥由谁证明?事实上存在一条证书链:

根 CA
  ↓ 签发
中间 CA
  ↓ 签发
网站证书

浏览器或操作系统维护着可信根列表。浏览器用中间 CA 的公钥验证网站证书,然后用上一级的 CA 的公钥验证中间 CA 证书,最后找到信任的根。

HTTPS 通信过程

前面提到对称加密适合处理大量数据,但需要安全地建立双方共享的秘密。这些工作主要在 TLS 握手中完成,HTTPS 通过 TLS 保护 HTTP 通信,不过不同版本的密钥交换过程并不完全相同。

TLS 1.2 中的 RSA 密钥交换

  1. 双方打招呼。浏览器发送 ClientHello,携带支持的 TLS 版本、算法和客户端随机数。服务器返回 ServerHello,选定参数、提供服务器随机数,随后发送网站证书。
  2. 浏览器验证证书,检查域名是否匹配、证书是否过期、证书链是否可信等。验证通过后就可以信任证书里的网站公钥。
  3. 浏览器生成秘密。浏览器生成一个预主密钥,用网站的 RSA 公钥加密后发送。服务器用私钥解密。
  4. 计算通信密钥。双方结合预主密钥、客户端/服务器随机数等信息分别计算通信使用的密钥。
  5. 确认握手并传输数据。双方利用本次握手建立的秘密和握手记录计算校验值。接收方独立计算预期结果,与收到的 Finished 内容比较,验证通过后再使用对称加密保护 HTTP 请求和响应。

TLS 1.3

在上述 RSA 密钥交换方式中,如果攻击者保存了当年的完整握手和加密业务数据,后来又拿到网站的 RSA 私钥,就可能恢复当时的通信密钥,解密 HTTP 请求和响应。

TLS 1.2 也支持 ECDHE,TLS 1.3 则移除了 RSA 密钥交换,但仍支持 RSA 签名。

以 TLS 1.3 使用 ECDHE 密钥协商的情况为例。双方各自生成临时密钥对,交换临时公钥,结合自己的临时私钥,分别计算出相同的共享秘密 S,再通过密钥派生得到通信所需的密钥。

RSA:
客户端生成 S → 加密发送 → 服务器解密得到 S

ECDHE:
双方各留临时私钥 → 交换临时公钥 → 各自算出相同的 S

之后:
S → 密钥派生 → 得到各种用途的密钥

协商出秘密后还不能证明对方身份可信。服务器发送证书,并用网站私钥对本次握手记录签名。浏览器先验证证书的域名、有效期和信任链,再用证书中的网站公钥验证握手签名,从而确认对方持有这张可信证书对应的私钥。这里有两种签名:CA 签的是网站证书,服务器签的是本次握手记录,后者不属于自签名证书。最后双方通过 Finished 校验握手。

与前面的 RSA 方式相比,网站私钥在这里负责身份认证,建立共享秘密则由临时密钥完成。共享秘密依赖本次连接的临时私钥,在临时秘密妥善销毁的前提下,事后拿到网站的长期认证私钥,也不能直接恢复历史通信密钥,这叫前向保密。

回到 CA 私钥泄露

现在可以理解 CA 私钥为什么重要了。攻击者拿到了中间 CA 私钥,就可以在证书允许的范围内,为自己控制的公钥签发冒充正规网站的证书。私钥失守后,不仅需要更换密钥,还需要让客户端停止信任问题证书链。

这也不意味着所有历史 HTTPS 流量都能被解密,正确使用临时密钥协商并妥善销毁临时秘密的连接,仍有前向保密提供保护。

这篇文章就到这里。