3、TLS 1.3握手优化:1-RTT握手、0-RTT握手、PSK机制、与1.2的对比
聊到TLS 1.3,我其实挺感慨的。以前调TLS 1.2的性能,总有种「拆东墙补西墙」的感觉。1.3出来之后,很多问题从根上就解决了。说白了,它把握手延迟从「两轮往返」压缩到了「一轮」,甚至「零轮」。这背后靠的就是1-RTT、0-RTT和PSK这套组合拳。
这一节,我们把这几个核心机制拆开揉碎,再跟1.2做个硬碰硬的对比。你想想看,一个连接能省掉一个RTT,对全球上亿用户来说,省下来的时间可不是一星半点。
3.1 1-RTT握手:把两轮压缩成一轮
TLS 1.3最直观的变化,就是握手从2-RTT降到了1-RTT。怎么做到的?
核心思路是:客户端在第一条消息里,就把自己的「底牌」亮出来。它不再等服务器的证书,而是直接带上自己支持的密码套件、密钥交换参数(比如ECDHE的公钥),甚至一个猜测的密钥。
我画了个对比图,你看一眼就明白了。
看到没?TLS 1.3的服务端收到ClientHello后,直接就能算出预主密钥,然后一口气把ServerHello、证书、Finished全发回去。客户端收到后也能立刻算密钥,紧接着就能发应用数据。一轮握手,搞定。
核心收益:每次新建连接节省1个RTT。对于跨洋访问(比如从中国到美国,RTT约150-200ms),一次握手就能省下150-200ms。我有个项目,全球节点延迟平均降了35%,就靠这个。
3.2 0-RTT握手:连第一轮都省了
1-RTT已经很快了,但TLS 1.3还留了一手——0-RTT。说白了,就是客户端在第一条消息里,直接带上加密的应用数据。服务端收到就能用,零等待。
这怎么做到的?靠的是PSK(预共享密钥)。
第一次握手时,服务端会给客户端一个Session Ticket,里面藏着PSK。客户端存下来。下次再连同一个服务端,客户端直接拿PSK加密数据,塞进ClientHello里发出去。
流程是这样的:
- 首次连接:正常1-RTT握手,服务端下发PSK(通过NewSessionTicket消息)。
- 后续连接:客户端在ClientHello中携带PSK标识,并直接发送0-RTT数据(early_data)。
- 服务端验证:如果PSK有效,服务端解密数据并响应,同时下发新的PSK。
我个人的习惯:在移动端API网关场景,我会优先开启0-RTT。用户App冷启动后重新连接,经常能省掉一个完整的RTT。用户体验提升非常明显。
但注意,0-RTT不是万能的。它有个天生的缺陷——重放攻击。因为客户端发出去的数据,服务端无法确认是不是重复发送的。比如一个扣款请求,如果被攻击者截获并重放,可能造成多次扣款。
避坑指南:我曾经在一个支付接口上踩过这个坑。0-RTT数据绝对不能用于幂等性要求高的操作。解决方案是:只允许GET请求使用0-RTT,或者让应用层自己做幂等校验(比如加请求ID)。
3.3 PSK机制:会话复用的核心
PSK(Pre-Shared Key)是TLS 1.3实现0-RTT和快速重连的基石。它本质上是一个对称密钥,由服务端生成,通过加密通道发给客户端。
PSK有两种模式:
| 模式 | 说明 | 典型场景 |
|---|---|---|
| Session Ticket | 服务端将PSK封装在Ticket中,客户端存储Ticket | HTTP/2长连接复用、移动App重连 |
| 外部PSK | 双方预先共享一个密钥(带外分发) | 专线、IoT设备预配置 |
我个人更推荐Session Ticket方式。它不需要额外的密钥分发流程,服务端可以灵活控制PSK的有效期和刷新策略。
PSK的生命周期管理有几个要点:
- 有效期:默认建议24小时。太短了频繁握手,太长了有安全风险。
- 刷新策略:每次使用PSK成功握手后,服务端应下发新的PSK。这叫「一次一密」。
- 存储安全:客户端必须安全存储PSK。在浏览器里,这是浏览器引擎的事;在App里,建议用Keychain或Keystore。
性能数据:我在一个日活千万的App上做过测试。开启PSK复用后,TLS握手时间从平均120ms降到了15ms(0-RTT场景)。服务器CPU负载也降了约40%,因为省掉了非对称加密计算。
3.4 TLS 1.2 vs 1.3:一张表说清楚
说了这么多,咱们直接上对比表。我习惯把差异点列清楚,方便做技术选型。
| 对比维度 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 握手延迟 | 2-RTT(完整握手) | 1-RTT(完整握手),0-RTT(复用) |
| 密码套件 | 支持几十种,含大量不安全套件 | 仅5种安全套件,移除RSA密钥交换 |
| 密钥交换 | 支持RSA、DH、ECDH | 仅支持ECDHE(前向安全) |
| 会话复用 | Session ID / Session Ticket | PSK(基于Session Ticket或外部PSK) |
| 0-RTT支持 | 不支持 | 支持(需应用层处理重放风险) |
| 证书传输 | 明文传输证书链 | 加密传输证书链(隐私性更好) |
| 降级保护 | 无强制降级检测 | 内置降级防护机制 |
| 部署现状 | 仍广泛使用,但正在被替代 | 主流浏览器和CDN已全面支持 |
你看,TLS 1.3几乎在所有维度上都优于1.2。但现实是,很多老系统还在跑1.2。为什么?兼容性。有些老旧客户端(比如Android 4.x、某些嵌入式设备)不支持1.3。
我的建议:服务端同时开启TLS 1.2和1.3,让客户端协商选择。Nginx配置里加一行 ssl_protocols TLSv1.2 TLSv1.3; 就行。既保证兼容性,又能让新客户端享受1.3的红利。
3.5 实战优化要点
最后,我总结几个实战中可以直接落地的优化点:
- 优先开启TLS 1.3:服务端和CDN都配置支持。这是性价比最高的优化。
- 合理使用0-RTT:只对幂等请求开放。非幂等请求走1-RTT。
- PSK有效期管理:设置合理的过期时间(建议24h),并实现自动刷新。
- 监控握手延迟:在APM中埋点,区分1-RTT和0-RTT的握手耗时。如果0-RTT占比低,说明PSK复用率不够。
- 证书优化:TLS 1.3虽然加密了证书,但证书链长度仍然影响性能。尽量用ECC证书,减少证书大小。