TLS 1.3 新特性:与 1.2 的硬碰硬对比

如果你之前调过 HTTPS 性能优化,应该对 TLS 1.2 的握手延迟深有体会。我之前在一个高并发 API 网关项目里,光 TLS 握手就占了连接建立时间的 60% 以上。那时候我们试过会话复用、OCSP Stapling,能做的都做了,效果还是有限。

TLS 1.3 出来后,我第一时间就把它接入了测试环境。说实话,第一次看到握手耗时从 200ms 降到 50ms 以内,还是挺震撼的。这一章咱们就来拆解一下,TLS 1.3 到底改了啥,能让性能提升这么多。

握手轮次:从 2-RTT 到 1-RTT

TLS 1.2 完整握手需要两次往返(2-RTT)。第一次交换 ClientHello 和 ServerHello + 证书,第二次交换 Finished 和 ChangeCipherSpec。你想想看,如果用户在上海,服务器在北京,光网络延迟就要 30ms,两次往返就是 60ms,再加上加解密计算,200ms 轻松就出去了。

TLS 1.3 的完整握手只需要 1-RTT。它把大部分密钥协商过程直接放在 ClientHello 里。客户端一发出去,就把自己支持的密钥协商参数和公钥都带上了。服务端收到后,立刻算出主密钥,然后在 ServerHello 里把证书和 Finished 一并返回。这样一次往返就搞定了。

核心差异: TLS 1.3 将密钥协商提前到第一条消息,省掉了整整一次往返。

0-RTT:我改的最多的地方

更狠的是 0-RTT——如果你之前和这个服务器建立过连接,第二次握手可以零往返。客户端直接拿之前保存的 PSK(预共享密钥)加密应用数据,跟着 ClientHello 一起发出去。服务器验证通过后,直接开始处理业务数据。

我在线上环境中经常用 0-RTT 优化首次请求的加载时间。不过要注意一点:0-RTT 存在重放攻击风险,幂等接口才适合。我曾经在一个下单接口上踩过坑,用户重复提交了两次订单,后来加了 nonce 才解决。

密码套件:砍掉了 90% 的垃圾

TLS 1.2 里定义了几十种密码套件,什么 ECDHE-RSA-AES128-GCM-SHA256、DHE-RSA-AES256-CBC-SHA……看着就头疼。而且很多套件本身就有漏洞,比如 CBC 模式的填充攻击。我之前做安全审计时,光清理这些老旧套件就花了三天。

TLS 1.3 砍掉了所有静态 RSA、DH、CBC 和 SHA-1。现在只保留了 5 种 AEAD 套件,全部用 ECDHE 或 DHE 做密钥协商,全部用 GCM 或 ChaCha20 做对称加密。说白了,选项少了,但每个都是安全的。

项目 TLS 1.2 TLS 1.3
握手轮次(新连接) 2-RTT 1-RTT
握手轮次(复用) 1-RTT(会话ID/会话票据) 0-RTT(PSK + 可选的 1-RTT)
密码套件数量 30+ 5
密钥交换算法 RSA、DH、ECDH(可选) 必须 ECDHE 或 DHE
对称加密 GCM、CBC、CCM、RC4 仅 AEAD(GCM/ChaCha20/CCM)
签名算法 RSA、ECDSA、DSS RSA-PSS、ECDSA、EdDSA
协商灵活性 静态参数列表 独立协商每个组件

移除静态 RSA 和 DH:告别密钥泄露噩梦

TLS 1.2 支持静态 RSA 密钥交换,意味着服务器私钥一旦泄露,之前所有会话流量都能被解密。我记得 2014 年 Heartbleed 漏洞爆发时,很多使用 RSA 的服务器都遭了殃。TLS 1.3 直接禁止了这种模式,所有握手必须使用 ECDHE 或 DHE 实现前向安全性。即使私钥泄露,也只能解密未来的会话,之前的记录是安全的。

避坑小贴士: 如果你还在用 TLS 1.2,务必关闭静态 RSA 和 DHE 密钥交换,只保留 ECDHE。很多旧库默认还开着。

精简握手流程:去掉冗余握手消息

TLS 1.2 握手有 10 条消息:ClientHello、ServerHello、Certificate、ServerKeyExchange、CertificateRequest、ServerHelloDone、ClientKeyExchange、CertificateVerify、ChangeCipherSpec、Finished。我数过,光 ChangeCipherSpec 就浪费了半个往返。

TLS 1.3 精简到 6 条:ClientHello、ServerHello、EncryptedExtensions、Certificate + CertificateVerify、Finished、客户端 Finished。注意 ChangeCipherSpec 被移除了,它只在兼容模式下需要发一个空消息。

<!-- TLS 1.2 完整握手(2-RTT) -->
ClientHello                →  
                          ← ServerHello + Certificate + ServerKeyExchange + ServerHelloDone
ClientKeyExchange + Finshed →  
                          ← ChangeCipherSpec + Finished

<!-- TLS 1.3 完整握手(1-RTT) -->
ClientHello (key_share + supported_versions) →  
                          ← ServerHello + EncryptedExtensions + Certificate + CertificateVerify + Finished
Finished                  →

SVG:TLS 1.3 vs TLS 1.2 握手流程对比

TLS 1.2 vs TLS 1.3 握手流程对比 TLS 1.2 ClientHello ServerHello Certificate + KeyExchange ClientKeyExchange + Finished ChangeCipherSpec + Finished TLS 1.3 ClientHello + key_share (可选:early_data for 0-RTT) ServerHello + EncryptedExtensions Certificate + CertificateVerify + Finished Finished → 应用数据开始 0-RTT模式:early_data可 随ClientHello直接发送 需要 2 次往返(2-RTT) 只需 1 次往返(1-RTT) 复用时可做到 0-RTT

性能数据对比:一次实测

我在 lab 环境用 curl 跑了简单测试,单次 HTTPS 请求(无复用)延迟如下:

协议 平均连接时间 TLS 握手时间 首字节时间
TLS 1.2 32 ms 128 ms 190 ms
TLS 1.3 30 ms 42 ms 72 ms
提升 ≈6% 67% 62%

这里 TLS 1.3 的握手时间只有 1.2 的三分之一。如果再加上 0-RTT 复用,握手时间几乎可以忽略不计。用于移动端或弱网环境,体验差距会更明显。

迁移建议:别踩我踩过的坑

  1. 彻底禁用 TLS 1.0/1.1:很多旧库还可能回退。我曾经在一个 Nginx 日志里发现,虽然配置了只允许 TLS 1.2 和 1.3,但客户端降级协商后竟然用上了 1.0。后来加了 ssl_protocols TLSv1.2 TLSv1.3; 才彻底锁死。
  2. 检查密码套件白名单:TLS 1.3 的套件名和 1.2 不同,别直接把 1.2 的配置复制进去。比如 TLS_AES_128_GCM_SHA256 在 1.3 才有效。
  3. 0-RTT 配套防重放:对于非幂等接口,务必在应用层加一次性令牌或时间戳验证。我曾经在支付回调里开了 0-RTT,结果压测时产生了重复订单。
注意: 部分老旧客户端(如 Android 4.x、旧版 curl 7.x)不支持 TLS 1.3。如果你的用户群体里有这类环境,建议降级时提示或自动回退到 TLS 1.2。

嗯,这一章的内容差不多了。从 2-RTT 到 1-RTT,再到 0-RTT,TLS 1.3 的握手性能提升是实打实的。下回咱们就实际动手,用 OpenSSL 1.1.1+ 搭建一个支持 TLS 1.3 的服务器,亲手验证这些特性。