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 一并返回。这样一次往返就搞定了。
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 握手有 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 握手流程对比
性能数据对比:一次实测
我在 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 复用,握手时间几乎可以忽略不计。用于移动端或弱网环境,体验差距会更明显。
迁移建议:别踩我踩过的坑
- 彻底禁用 TLS 1.0/1.1:很多旧库还可能回退。我曾经在一个 Nginx 日志里发现,虽然配置了只允许 TLS 1.2 和 1.3,但客户端降级协商后竟然用上了 1.0。后来加了
ssl_protocols TLSv1.2 TLSv1.3;才彻底锁死。 - 检查密码套件白名单:TLS 1.3 的套件名和 1.2 不同,别直接把 1.2 的配置复制进去。比如
TLS_AES_128_GCM_SHA256在 1.3 才有效。 - 0-RTT 配套防重放:对于非幂等接口,务必在应用层加一次性令牌或时间戳验证。我曾经在支付回调里开了 0-RTT,结果压测时产生了重复订单。
嗯,这一章的内容差不多了。从 2-RTT 到 1-RTT,再到 0-RTT,TLS 1.3 的握手性能提升是实打实的。下回咱们就实际动手,用 OpenSSL 1.1.1+ 搭建一个支持 TLS 1.3 的服务器,亲手验证这些特性。