TLS 1.2 与 TLS 1.3 握手的主要区别

这个问题我面试时几乎必问。为什么?因为握手流程的差异,直接反映了 TLS 协议从“兼容性优先”到“安全+性能优先”的转变。我当年在一个金融支付项目中,硬着头皮把业务从 TLS 1.2 迁移到 1.3,中间踩了不少坑。今天就跟你聊聊,这两个版本握手到底差在哪。

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

先说最直观的差别。TLS 1.2 完整握手需要两次来回(2-RTT)。1.3 只用了一次,而且它还提供了 0-RTT 数据发送,前提是你之前已经建立过会话。嗯,我知道你可能会问:那一趟省在哪了?

看下面这个表格就清楚了。我把两个版本的核心握手步骤列出来,你可以对比着看。

步骤TLS 1.2TLS 1.3
1Client → Server: ClientHello
(密码套件、随机数、Session ID)
Client → Server: ClientHello
(密码套件、key_share、PSK)
2Server → Client: ServerHello + Certificate + ServerHelloDoneServer → Client: ServerHello + EncryptedExtensions + Certificate + CertificateVerify + Finished
3Client → Server: ClientKeyExchange + ChangeCipherSpec + FinishedClient → Server: (证书验证) + Finished
4Server → Client: ChangeCipherSpec + Finished(握手结束,开始应用数据)
RTT21
我的小经验:在 TLS 1.3 的 ServerHello 之后立即就进入加密阶段了。ChangeCipherSpec 这个字段在 1.3 里只作为兼容性保留,实际不再参与加密状态切换。很多抓包工具初看 1.3 的握手会一脸懵,其实只要记住:ServerHello 之后的第一个 Finished 消息就是加密的了。

密码套件与密钥交换:1.3 砍掉了哪些“老古董”?

第二个大区别,密码套件协商方式变了。TLS 1.2 允许服务器从一堆套件里选,密钥交换可以用 RSA、DHE、ECDHE,甚至 static DH。嗯,这里就有安全风险。比如 RSA 密钥交换不支持前向安全性(PFS)。我曾在一次审计中发现,公司用了 TLS_RSA_WITH_AES_128_CBC_SHA,私钥一旦泄露,历史流量全完蛋。

TLS 1.3 直接砍掉了所有非 PFS 的密钥交换,强制只有 DHE 与 ECDHE。同时,加密套件从几十种精简到5 种,全是 AEAD 模式(AES-GCM、ChaCha20-Poly1305等)。你看这个变化——

  • TLS 1.2 常见套件举例:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(签名+密钥交换+加密+MAC,四个部分混在一起)
  • TLS 1.3 套件举例:TLS_AES_128_GCM_SHA256(只定义加密+哈希,密钥交换由扩展 key_share 独立协商)

关键思维转变:1.3 把“密钥交换算法”从密码套件里剥离了。签名算法在 CertificateVerify 独立协商,密钥交换在 key_share 扩展里搞定。密码套件只负责对称加密 + HMAC。这样每个环节的妥协空间就小了。

会话恢复:从 Session ID / Ticket 到 PSK

最早 TLS 1.2 用 Session ID 做恢复,服务器得存状态。后来有了 Session Ticket(RFC 5077),客户端自己存加密票证。但无论哪种,恢复时都要额外交换一次。TLS 1.3 直接抛出了 PSK(预共享密钥)机制。客户端在手握之前的主密钥后,可以直发一个 PSK 扩展,把握手压缩到 0-RTT(当然 0-RTT 有重放风险,要应用层兜底)。

我曾经处理过一个高并发 API 网关,客户端每次连接都走完整握手,延迟 80ms。后来切到 PSK 恢复,大部分连接直接 0-RTT 发数据,延迟降到 15ms。收益非常明显,但要注意:0-RTT 的数据不能保证幂等操作,比如支付要加防重放。

其他重要区别

除了上面这些,还有几个细节。我说几个我踩过的——

  • CertificateVerify 签名内容:1.2 签的是握手消息的 hash,但 1.3 签的是 transcript hash(包含双方所有握手消息)。攻击者更难篡改协商过程。
  • HelloRetryRequest:1.3 引入了这个新消息。当服务器不支持客户端发来的密钥交换组时,它会要求客户端换个曲线重发 ClientHello。我调试时第一次看到这个,还以为客户端发错了。
  • ChangeCipherSpec 的地位:1.3 里它的出现仅用于兼容中间盒,不会触发任何加密状态迁移。如果你在抓包时看到多出来的 CCS,直接忽略就好。
避坑提醒:我曾经在生产环境强制开启 TLS 1.3 后,发现某老版 CDN 代理不认识 key_share 扩展,直接丢包了。解决方案是降级回 1.2 兼容或者升级 CDN。所以在排障时,一定先看中间件的版本是否支持 1.3。

一张图理解握手差异

TLS 1.2 与 TLS 1.3 握手流程对比 Client Server (1.2) ClientHello ServerHello Certificate + … ClientKeyExchange ChangeCipherSpec+Fin ChangeCipherSpec + Finished RTT1 RTT2 Client Server (1.3) ClientHello + key_share ServerHello EncryptedExtensions Certificate+CertVerify+Fin Finished RTT1 应用数据可发 说明 TLS 1.2 需要两趟 RTT 完成握手(密钥交换与认证分开);TLS 1.3 一趟搞定, 同时支持 0-RTT 数据发送(当有 PSK 时可跳过第一趟握手)。

上面这张图应该能帮你直观理解“少一趟”的含义。1.3 把证书和 Finished 打包在 ServerHello 之后一起发,客户端拿到就能直接算密钥,不用再交换 ClientKeyExchange 了。