TLS/SSL协议深度解析:TLS 1.2与TLS 1.3的握手流程对比,密码套件协商机制

聊到密码套件,就绕不开TLS握手。很多搞性能优化的朋友,一上来就盯着加密算法本身,却忽略了握手阶段的开销。我见过不少项目,密码套件选得挺强,结果握手延迟直接拖垮了用户体验。说白了,TLS握手就是客户端和服务器在“对暗号”,而这个暗号怎么对、对几轮,直接决定了你的连接速度。

一、TLS 1.2 握手流程:经典但有点啰嗦

TLS 1.2 从2008年服役到现在,依然很能打。但它的握手流程,说实话,有点“话多”。

我习惯把TLS 1.2握手拆成两个阶段:明文阶段和加密阶段。

1.1 明文阶段(前两轮)

  • ClientHello:客户端发个招呼,带上自己支持的TLS版本、密码套件列表、一个随机数(Client Random)。
  • ServerHello + 证书:服务器选好密码套件,发回自己的随机数(Server Random)和数字证书。

嗯,这里要注意。证书里包含服务器的公钥,但公钥本身不加密,只是用来后面做密钥交换的。

1.2 加密阶段(后三轮)

  • 密钥交换:客户端验证证书后,生成一个Pre-Master Secret,用服务器公钥加密发过去。服务器用自己的私钥解密。
  • ChangeCipherSpec:双方各自用Client Random、Server Random和Pre-Master Secret算出会话密钥。然后互相通知:“我要开始加密通信了”。
  • Finished:最后发一条加密的“校验消息”,确认握手成功。

关键点:TLS 1.2 握手需要 2-RTT(两轮网络往返)。第一轮交换Hello和证书,第二轮交换密钥和Finished。如果网络延迟是50ms,光握手就耗掉100ms。

二、TLS 1.3 握手流程:少说多做

TLS 1.3 在2018年发布,最大的改进就是“砍流程”。我当年第一次看到1.3的握手规范时,心里就想:早该这么干了。

2.1 1-RTT 握手(常规模式)

  • ClientHello:客户端不仅发Hello,还直接把自己的密钥交换参数(比如DH公钥)带过去。相当于把TLS 1.2的前两轮合并了。
  • ServerHello + 证书 + 密钥交换:服务器收到后,直接回复自己的密钥参数和证书。双方此时已经能算出会话密钥了。
  • Finished:服务器发完Hello后,立刻发加密的Finished。客户端收到后也能立刻发加密数据。

你看,TLS 1.3 只用 1-RTT 就完成了握手。少了整整一轮。

2.2 0-RTT 握手(会话恢复模式)

如果你之前和服务器握过手,TLS 1.3 允许你在第一个包里就直接发加密数据。这叫 0-RTT。不过,0-RTT 有重放攻击的风险,我一般只在幂等请求(比如GET查询)里用。

我的经验:在移动端弱网环境下,TLS 1.3 的 0-RTT 能帮大忙。我曾经在一个IoT项目里,把设备首次连接时间从1.2秒降到了400毫秒,靠的就是0-RTT + 会话票据。

三、密码套件协商机制:到底谁说了算?

密码套件协商,说白了就是客户端和服务器在“讨价还价”。客户端说:“我支持这些”,服务器说:“我选这个”。

3.1 协商流程

  1. 客户端在ClientHello里列出自己支持的密码套件列表(按优先级排序)。
  2. 服务器收到后,从列表里挑一个自己也能支持的。如果都不支持,就返回握手失败。
  3. 服务器把选中的套件ID放在ServerHello里发回去。

这里有个坑:服务器说了算。客户端只能建议,不能强制。我遇到过好几次,客户端明明支持更强的套件,但服务器配置了弱套件,结果协商出来一个过时的算法。

避坑指南:我曾经在一个金融项目里,发现服务器默认启用了TLS_RSA_WITH_AES_128_CBC_SHA。这个套件不支持前向安全性(PFS),而且CBC模式有padding oracle攻击风险。后来我强制服务器只启用ECDHE + AEAD套件,才解决问题。

3.2 TLS 1.2 与 TLS 1.3 的套件差异

特性 TLS 1.2 TLS 1.3
套件数量 37个(IANA注册) 5个(强制实现)
密钥交换 RSA、DH、ECDH、DHE、ECDHE 仅支持ECDHE和DHE
认证与加密 可分离(如RSA + AES-CBC + HMAC) 强制AEAD(如AES-GCM、ChaCha20-Poly1305)
前向安全性 可选(RSA套件不支持) 强制(所有套件都支持PFS)

你想想看,TLS 1.3 把套件砍到只剩5个,意味着什么?意味着协商几乎不需要“讨价还价”,服务器闭着眼睛都能选。这也是1.3握手更快的原因之一。

四、握手流程对比图

下面这张图,我画了TLS 1.2和1.3的握手对比。左边是1.2,右边是1.3。你看一眼就明白差距了。

TLS 1.2 与 TLS 1.3 握手流程对比 TLS 1.2 vs TLS 1.3 握手流程对比 TLS 1.2(2-RTT) ClientHello ServerHello + 证书 密钥交换(Pre-Master) ChangeCipherSpec Finished TLS 1.3(1-RTT) ClientHello + 密钥参数 ServerHello + 证书 + 密钥 Finished(加密) 关键差异 • TLS 1.2 需要 2 轮往返(2-RTT),TLS 1.3 只需 1 轮(1-RTT) • TLS 1.3 将密钥交换参数提前到 ClientHello,减少了交互次数 • TLS 1.3 移除了 ChangeCipherSpec 和独立密钥交换步骤

五、性能影响:RTT 是最大的敌人

你可能觉得,差一个RTT能有多大区别?我举个例子。假设你的服务器在美西,客户端在中国,网络延迟200ms。

  • TLS 1.2:2-RTT = 400ms 握手延迟。加上应用数据请求,首字节时间轻松超过500ms。
  • TLS 1.3:1-RTT = 200ms 握手延迟。首字节时间能压到300ms以内。

这还没算证书链验证和密钥计算的时间。在移动端或者卫星链路上,这个差距会被放大到秒级。

性能建议:如果你的设备CPU性能有限(比如IoT设备),TLS 1.3 的另一个好处是减少了计算量。它强制使用ECDHE密钥交换,避免了RSA解密的高开销。我测过,在ARM Cortex-M4上,TLS 1.3 握手比1.2快了约40%。

六、密码套件协商的实战细节

最后聊点实际的。你在配置服务器时,密码套件列表的顺序很重要。客户端发来的列表是按优先级排的,但服务器有最终决定权。我建议:

  1. 优先启用TLS 1.3:它的套件少而精,协商几乎零延迟。
  2. TLS 1.2 只保留ECDHE + AEAD套件:比如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。去掉所有RSA密钥交换和CBC模式套件。
  3. 注意兼容性:有些老旧客户端不支持TLS 1.3。我一般会同时开启1.2和1.3,让服务器根据客户端能力自动降级。

一个小技巧:用OpenSSL测试你的服务器密码套件协商情况:openssl s_client -connect yourserver.com:443 -tls1_2 -cipher 'ECDHE+AESGCM'。如果握手失败,说明你的配置有问题。