2. TLS 1.3 握手协议:1-RTT 握手机制与原理详解
好,咱们进入正题。
TLS 1.3 的握手,最核心的变化就是快。说实话,我第一次在测试环境里把 1.3 跑起来的时候,看到那个交互日志,真是愣了一下——怎么这么快就结束了?之前做 TLS 1.2 性能调优时,光是握手那几步,就得折腾半天。
这一章,我就带你把它拆开看明白。
2.1 握手到底在干什么?
握手,说白了就是客户端和服务器之间建立信任的过程。它要完成三件事:
- 身份认证:确认对方是不是真的那个家伙。
- 密钥协商:商量出一个只有你我知道的对称密钥。
- 参数确认:约定用什么加密算法、什么协议版本。
在 TLS 1.2 时代,这一套走下来要 2-RTT,也就是两个网络来回。1.3 把它压到了 1-RTT,这就是质的飞跃。
核心差异:TLS 1.3 将握手的 RTT 从 2 次减少到 1 次,直接省掉了一半的交互时间。对于首屏加载、弱网环境,这个优化是决定性的。
2.2 1-RTT 是怎么做到的?
秘密就在于“提前量”。
你想想看,TLS 1.2 的握手为什么慢?因为它需要先交换证书,然后才做密钥协商。这两个步骤是串行的,必须等证书验证完了,才能开始商量密钥。
1.3 把这两个步骤合并了。客户端在第一次发送的消息里,就直接把密钥协商的参数带过去。服务器收到后,也能立刻给出响应。这样,一个来回就把事全办完了。
我个人的习惯是把 1-RTT 握手分成两个阶段来理解:
- 第一阶段:密钥协商(其实是“预协商”阶段)
- 第二阶段:握手完成与验证
下面这张图展示了它的完整流程,我建议你对着看,会清晰很多。
别被图上的箭头吓到。说白了,整个流程就两个关键点:
记住这两点:
- ClientHello 发出去的时候,就把自己的“公钥”扔过去了。服务器拿到就能算密钥,不用等证书。
- 服务器在 ServerHello 里同时返回了它自己的“公钥”和证书。客户端拿到就能算出和应用层一样的密钥,完成握手。
2.3 关键的“Key Share”扩展
这个 Key Share 是 1-RTT 的发动机。它是个扩展字段,客户端把它塞在 ClientHello 里。
举个例子,假设客户端想用 X25519 曲线做密钥交换,它就可以这样写:
// 客户端 ClientHello 中的 Key Share 扩展
Extension: key_share (type=51)
KeyShareClientHello:
ClientShare:
Group: x25519
Key Exchange: d0b... (32 字节的公钥)
服务器收到后,直接用自己的私钥加上客户端的公钥,就能算出共享密钥。不用额外再走一次密钥交换的流程。这就是 1-RTT 的秘密所在。
技术细节:Key Share 扩展中,客户端可以同时发送多个群组的公钥(比如 x25519 和 P-256 各一个)。服务器选择一个它支持的,这样能避免额外的协商延迟。
2.4 支持的密钥交换方案
TLS 1.3 只支持一种密钥交换方式:静态 Diffie-Hellman(DH)。它只支持 ECDHE 和 DHE。
我遇到过有人问:“那 RSA 密钥交换呢?” 答案是 TLS 1.3 已经彻底删掉了。RSA 密钥交换不支持前向安全性,这是个大坑。
下面是两种方案的对比:
| 方案 | 群组类型 | 性能特点 | 推荐场景 |
|---|---|---|---|
| ECDHE | 椭圆曲线(如 x25519, P-256) | 计算速度快,密钥短 | 移动端、资源受限设备、低延迟场景 |
| DHE | 有限域(如 ffdhe2048) | 计算速度慢,密钥长 | 兼容性兜底(很少使用) |
我个人强烈建议优先使用 ECDHE。尤其是 x25519,它在性能和安全上都是一流的。我在生产环境里测过,同样的硬件,用 x25519 比用 P-256 快了将近 10%。
避坑指南:我以前在一个项目里,服务器配置了 DHE 作为默认方案,结果在高并发下 CPU 直接顶不住了。后来改成 ECDHE + x25519,握手延迟从 15ms 降到了 3ms。记得检查你的服务器配置,不要用 DHE 作为主要方案。
2.5 握手完成后的验证
握手结束后,客户端和服务器已经算出了相同的握手密钥和主密钥。这时候还需要做一次验证,防止中间人攻击。
验证方式很简单:双方各自用握手密钥加密一个“Finished”消息。如果对方能正确解密并回复,就说明密钥是对的,握手成功。
嗯,这里要注意:Finished 消息里包含的是整个握手消息的 MAC。如果中间有人篡改了消息,MAC 就对不上,握手就会失败。
2.6 配置与调优建议
你如果想拿到 TLS 1.3 1-RTT 的全部性能收益,有几个地方值得关注:
- 优先支持 ECDHE 曲线:特别是 x25519。如果客户端不支持,才考虑 P-256。
- 不要同时支持太多曲线:
key_share扩展里每多一个曲线,ClientHello 包就大一点。我建议只留 2-3 个主流的。 - 启用 0-RTT:这其实是 1-RTT 的进一步优化,可以在某些场景下彻底免掉握手。下节课会详细说。
- 合理配置证书链:虽然证书不用在握手时传递,但验证证书的耗时不能忽略。选择高效的证书格式和算法也很重要。
小经验:如果你想测试 1-RTT 的性能,用 curl 加上 --tlsv1.3 --tls-max 1.3 参数,然后抓包看 SSLKEYLOGFILE。你会看到 ClientHello 和 ServerHello 之间只有一个 RTT。
好了,1-RTT 握手的原理就是这样。从 2-RTT 到 1-RTT,TLS 1.3 通过 Key Share 扩展,把密钥协商提前到了第一次交互中。这个设计,说白了就是把“等待”变成了“同步”。