TLS 握手究竟做了什么?

What happens in a TLS handshake?

ZingLix May 7, 2019

随着 HTTPS 的普及,TLS 这一个单词也出现的更为频繁,那么什么是 TLS 呢?TLS 又如何使得 HTTP 传输变得安全呢?

TLS,全称 Transport Layer Security,安全传输层协议,前身为 SSL (Secure Socket Layer,安全套接层),位于 TCP 与应用层之间。HTTPS 相对于 HTTP 来说,协议本身并未改变,而是在 TCP 与 HTTP 间添加了一层 TLS 用作加密以保证信息安全。

对于明文传输的信息,会面临如下几种风险

  • 窃听
  • 篡改
  • 伪造

TLS 通过多重措施解决了上述三个问题。

对称与非对称加密

在叙述 TLS 之前值得一提的是两种加密方式。以往的经验告诉我们试图通过一种方法进行加密是不可靠的,因为一旦该加密方法被公开那么就相当于完全被破解,而加密方法被广泛使用后就很难继续保持为秘密。现代加密技术都将加密方法公布于众,使用一个叫 密钥 的东西进行加密/解密,这样即使某个密钥被泄露(不是破解),其他加密信息依旧可靠。

以此为基础出现了对称与非对称加密两种技术。对于对称加密,加密与解密用的是同一个密钥,这一方法最大的挑战是如何安全传输密钥。另一种非对称加密,会产生一对密钥,分别称为公钥和私钥,两者容易生成,但难以互相推导出另一个。用公钥加密的信息只能通过私钥解密,这样公钥就可以任意在网上传输,而私钥全程不需被传输,从而解决了对称加密的弊端,但是带来的是性能上的挑战。

后续我们可以看到 TLS 是如何同时使用两者,从而保证安全并不降低性能。

握手目的

TLS 握手过程主要是为了完成:

  • 加密组件协商
  • 服务器身份认证(可选的可以进行客户端身份认证)
  • 会话密钥信息交换

这一过程通信双方协商交换了所有之后加密通信所必须的信息,才能使得之后加密通信可行。

握手流程

TLS 握手可以分为如下几步

  1. 客户端发起连接,客户端带上自己产生的随机数和支持的加密套件向服务器发出 Client Hello 请求。

  2. 服务器收到请求后带上自己的随机数以及选择的加密套件返回 Server Hello 信息。在之后服务器发送自己的证书。此时服务器也可要求客户端出示证书。发送完成后发送 Server Hello Done 信息。

  3. 客户端通过验证服务器证书是否可靠以决定是否继续通信,若不可信则关闭连接。

  4. 若认为可信客户端则会生成一个新随机数,称为预主密钥(Pre Master Key),用于之后生成会话密钥,通过来自于证书的公钥进行加密提供给服务器。

  5. 之后客户端会再传递一个 Change Cipher Spec 表示之后信息会使用新的会话密钥(session keys)加密信息和哈希。之后客户端发送 Client finished 握手结束。

  6. 服务器收到数据后解密得到预主密钥,计算得出会话密钥,然后同样向客户端发送 Change Cipher SpecServer finished

至此通信双方握手结束,此时双方都有了用于对称加密的会话密钥,之后通信都使用该密钥加密。

上述为 TLS 1.2 的握手流程,TLS 1.3 对握手流程进行了简化,此文有详细介绍

上图是访问本博客时握手抓包所得到的结果。

这里还有没提到的两个随机数和预主密钥,此处介绍的是 RSA 算法,通信双方可以使用这三者用同一算法计算得出最后对称加密所使用的密钥。因为证书中的公钥是静态的,所以必须找到一种方法使得每次协商出的密钥不同,因此必须使用随机数。另外又由于对于对方主机的随机数随机性不可信(例如可能对方始终只用一个相同的数或存在规律),这样很有可能会被猜出该随机数,所以用三个随机数来生成密钥。

另一种密钥交换算法为 Diffie–Hellman 算法,客户端和服务器都生成一对公私钥,然后将公钥发送给对方,双方得到对方的公钥后,用数字签名确保公钥没有被篡改,然后与自己的私钥结合,就可以计算得出相同的密钥。

这里还会用到一个称为密钥派生算法(key derivation function, kdf)的函数,用于从主密钥等秘密信息中派生出多个密钥,提高密钥的随机性,保证其安全性。

简单来说就是 TLS 在通信过程中使用的是对称加密,握手目的就是让双方协商获得加密的密钥。密钥由一个函数产生,该函数参数是双方协商的关键,这一信息通过非对称加密的方式传输,同时利用非对称加密特性做了身份认证,从而既保证了性能又保证了安全性。

证书认证

握手过程中,客户端收到证书后需要验证证书的可靠性。这里证书也用到了非对称加密,之前说过公钥只能用来加密,私钥只能用来解密,对于证书来说可以将私钥公之于众,而公钥自己存储,这样就只有自己可以加密,别人只能解密。证书就是加密后的数据,其他人解密后信息正确即可保证证书来自加密人而非第三方。

事实上生成密钥对时得到的两个数字是平等的,即任意一个加密的都可用另一个解密,成为公钥还是私钥取决于将哪个公开。对于证书来说,用来解密的因为是公之于众的所以依旧称之为公钥,同理用来加密的称为私钥。

但这里还有一个问题是,我如何确保证书的确来自正确的人?因为任何人都可以生成一个密钥对,然后用别人的信息来生成一张假证书,那么如何确定这不是一张伪造的证书呢?这里 TLS 用到了 CA (Certificate Authority),是专门用来签发证书的可靠机构。对于 TLS 的证书,其实际上是一个证书链。

上图是本博客的证书信息,可以看到是由 Let’s encrypt 签发的,而其上还有一层 DST Root CA,这一层就是根 CA。被信任的根 CA 证书是默认随 Windows、Chrome 等软件分发的。当客户端验证证书时会一层一层认证直到根,此时会与系统中值得信任的证书比较,如果来自可信的 CA 那么就认为该证书链均可信。

之所以认为 CA 签发的证书可信是因为签发的过程中 CA 会验证申请者的身份,从最简单的网站所有权到更严格的申请者书面提交信息认证,从而确保签发的目标是真正拥有该网站的人。而且 CA 的私钥通常都是严格保护起来的,而且签发流程被严格监督,如果存在滥发证书的现象或私钥泄露那么该根证书就会被吊销,以此为基础的证书链都被认为不可靠,通过这样的强制力以避免伪造证书的可能。

伪造证书的关键就是无法取得根 CA 的认证,从而无法得到信任。但如果系统中存在一个伪造者的根证书,伪造者以此签发网站的证书都会被系统信任,这样会导致信息可以被攻击者伪造,所以不可轻易向系统添加不知来源的证书。

上图是访问本博客时获得的证书信息,其中携带了各种证书基本的信息,例如域名、有效时间等等。其中最为关键的是签名 Signature,是将本证书进行哈希然后用私钥加密的,客户端会通过相同的哈希算法与解密得到的信息比对,如果相同说明证书未被篡改,加上证书链机制保证证书可信。

总结

现在再来回头看 TLS 要解决的三个问题:窃听、篡改和伪造。

  • 窃听:握手过程中信息交换是明文,但是只是关于加密套件及证书信息的交换,第三方获得也没有用。而关键的可获得密钥的预主密钥则通过非对称加密方式传输,握手完成后使用对称加密,窃听者均无法获得密钥所以无法窃听。
  • 篡改:握手过程中,证书有哈希这一过程无法被篡改,其他信息被篡改会导致之后双方通信无法进行。握手完成后,因为无法获得密钥进行加密,所以虽然可以修改包内容,但无法变成攻击者预期的内容。
  • 伪造:密钥协商中的预主密钥用的是证书公钥加密的,其中证书是被确定可信的,而只有证书拥有者能解密,所以密钥只有通信双方拥有,因此不可能被伪造。

其中值得一提的是,尽管看似可以修改包内容依旧很危险,但这只会导致通信被干扰。不过毕竟都能修改包内容了,那我也能选择直接不转发包,这就不是 TLS 所能解决的范畴了,只要不能变成攻击者所精心选择的内容就无所谓了,因此 TLS 出色完成了使信息无法被篡改的目标。

可以说 TLS 成功完成了通信信息安全的问题,可以确保上层应用传输的数据不被第三方获得,但是依旧存在一些小问题。

上图是 Client Hello 的抓包内容,可以看到其中有访问的地址。握手过程中信息是明文的,而对于 SNI 信息。这一信息本应位于 HTTP 的包头,但是在握手时,服务器必须知道对应于哪个应用,因此客户端必须提供该信息,但又没有任何可用来加密的信息,所以 SNI 只能明文传输,导致第三方依旧可以知道访问的时哪个网址。

这篇文章介绍了弥补 SNI 明文传输的措施: ESNI 与 ECH

好在 TLS 依旧在改进,很多问题都有了提议,相信很快就会有更安全的因特网出现。