九年过去了,TLS 1.3 带来了什么?

What's new in TLS 1.3?

ZingLix April 5, 2021

距离 TLS 1.2 发布时隔九年,其中经过四年漫长的商定,TLS 1.3 正式面世,其设计目标简单来说就是让数据传输更快更安全。

更安全

TLS 采用的是混合的加密流程,也就是在通信过程中采用对称加密,该加密的密钥通过非对称加密协商得到。其中用 MAC 算法进行完整性校验,证书体系进行身份认证。这一流程听上去很完美,但现实却并不总是这么美好。

前向安全

前向安全(Forward Secrecy, FS),也被称为完美前向保密(Perfect Forward Secrecy, PFS),是密码学中的一个概念,指的是长期使用的主密钥泄露不会导致过去会话的密钥泄露,换句话说如果攻击者保存了过去加密会话的流量,同时获得了私钥,依旧无法解密所有会话。

前向安全是 TLS 1.3 中最关键的特性之一。

RSA 与 DH

TLS 握手中关键的部分就是协商后续通信的密钥,分为 RSA 和 Diffie–Hellman 两种密钥交换算法。

RSA 是常用且简单的一个交换密钥的算法,即客户端决定密钥后,用服务器的公钥加密传输给对方,这样通信双方就都有了后续通信的密钥。

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

这两种方法曾经都可以用于交换密钥,但是 RSA 存在一个缺点,就是它不是前向安全的,因为当服务器私钥泄露后,所有的通信都可以被解密,安全性完全依赖于服务器私钥的安全性。对于 DH 算法来说,只要每次通信生成新的密钥对,那么解密的密钥每次通信都会不同。

为了保证前向安全,TLS 1.3 中 移除了 RSA 算法,Diffie–Hellman 是 唯一 的密钥交换算法。

更多潜在的隐患

除了上述提到的密钥交换算法存在问题外,部分对称加密算法和数字签名算法也存在漏洞,例如 CBC、RC4、SHA1。

先计算 MAC 再加密的方法也存在安全隐患,因此使用 MAC 进行块加密和流加密的机制也被废弃,AEAD 成为了唯一的选择。

同时,对加密的 TLS 报文进行压缩存在漏洞,现在不再允许压缩。DH 本身具备前向安全性,不再允许重协商。过去 Change Cipher Spec 决定了何时开始传输加密数据,这一机制也被废除,因为可以随着握手完成自然而然开始。

此外,过去握手服务器仅对部分内容进行签名,而协商使用算法套件的部分并没有签名,有攻击就会被篡改这部分,被迫通信双方使用弱的密码和弱的算法套件。但这些也成为了历史,TLS 1.3 中将签名整个握手,握手阶段的明文部分大大减少。

可惜 SNI 依旧是明文传输,这篇文章介绍了弥补措施 ESNI 与 ECH

太多选择不是件好事

TLS 协商的算法套件涵盖了几乎一个连接所有的细节,一个算法套件包括:

  • 支持的证书类型
  • 用于导出密钥的哈希算法(SHA1, SHA256,…)
  • 消息认证(MAC)函数(HMAC with SHA1,SHA256,…)
  • 密钥交换算法(RSA,ECDHE,…)
  • 对称加密的算法(AES,RC4,…)
  • 加密算法工作模式(CBC,…)

过去的 TLS 每一个部分都提供了大量的选择,加上各种组合,就形成了大量诸如 DHE-RSA-AES256-GCM-SHA384 这样又臭又长的名称。

TLS 1.3 简化了算法套件的协商,除了移除了所有已知不安全的算法外,同时将算法套件改成了正交的三个部分:

  • 加密算法和 HKDF 哈希算法
  • 密钥交换算法
  • 签名算法

HKDF(HMAC based Key Derivation Function)为的是解决生成密钥的随机性不够,因此可以将握手阶段的哈希值与协商的密钥通过该函数获得安全性更强的密钥。

相较于 1.2 三十多种算法套件,1.3 目前只有这五种套件:

  • TLS_AES_256_GCM_SHA384
  • TLS_CHACHA20_POLY1305_SHA256
  • TLS_AES_128_GCM_SHA256
  • TLS_AES_128_CCM_8_SHA256
  • TLS_AES_128_CCM_SHA256

更快

让我们先回忆下 TLS 1.2 握手的耗时:

  1. 客户端 \(\rightarrow\) 服务器:Client Hello(支持的加密套件、密钥协商材料等)
  2. 客户端 \(\leftarrow\) 服务器:Server Hello(证书、选择的加密套件等)
  3. 客户端 \(\rightarrow\) 服务器:Pre Master Key(RSA)或客户端公钥(DH)

至此双方都获得了计算会话密钥所有所需的材料,后续可以开始使用协商的密钥继续通信,这个过程耗费 1.5 RTT。

1-RTT

没错,TLS 1.3 减少了半个 RTT,这得益于密钥协商的简化。现在我们只能选择 DH,服务器可支持的参数也少的如此容易去猜,因此客户端发送第一条消息的时候,就可以直接带上公钥和所有密钥协商材料,而不是等到服务器选择采用的套件后再发送。

因此,现在握手的流程可以简化为:

  1. 客户端 \(\rightarrow\) 服务器:客户端的公钥
  2. 客户端 \(\leftarrow\) 服务器:证书、服务器的公钥

在极少数的情况遇到服务器不支持,那么会发送一个 HelloRetryRequest 来重试连接,但这种情况极其罕见。

0-RTT

是的,你并没有看错,不需要 RTT 就可以直接发送加密的内容,就像 HTTP 一样。

当然,前提是客户端已经与服务器建立过一次连接,之后双方可以导出一个叫 预共享密钥(Pre-Shared Key,PSK)的东西,服务器发给客户端一个会话 id 或者会话凭证用于后续恢复连接,然后使用上一次通信的参数继续通信。

0-RTT 虽然在性能上很棒,但是也付出了前向安全的代价,此外还有一个问题就是重放攻击。因为发送前双方都没通信,数据不具备交互性,因此攻击者可以捕获包后重新发送,服务器很可能将其认为有效并接受,如果该操作是存在副作用的,例如会修改服务器状态,那么就会是有风险的。

因此,对于客户端来说,应当只将安全的数据放在 0-RTT 数据中,例如 HTTP GET 请求。

总结

简单来说,TLS 1.3 又是一次人类对安全性和 RTT 的斗争。实现了前向安全,只留下目前安全的算法,连接所需的耗时进一步降低至 1-RTT,特殊情况下可以实现 0-RTT。