3. TLS 1.2 与 TLS 1.3 的区别与演进
说实话,很多同学刚接触 TLS 时,总觉得版本号就是个数字,升级只是修补漏洞。但在我实际排查接口问题的经历里,版本不匹配恰恰是握手失败的常见原因。TLS 1.3 不是 1.2 的简单补丁,它改了握手的“剧本”。这一节我带你梳理清楚。
3.1 从 1.2 到 1.3:握手流程的瘦身
TLS 1.2 的完整握手要 2-RTT(两个往返)。客户端先发 ClientHello,服务端回 ServerHello + 证书 + ServerHelloDone,然后客户端再发 ClientKeyExchange、ChangeCipherSpec、Finished,服务端再回 ChangeCipherSpec、Finished。你想想看,一次连接光握手就耗掉两趟网络开销,高延迟场景下非常难受。
TLS 1.3 把流程压缩到 1-RTT,甚至 0-RTT(带早期数据)。核心变化是:客户端在 ClientHello 里直接把支持的密钥协商参数(KeyShare)发过去。服务端拿到后当场算出共享密钥,ServerHello 之后就能加密传输了。握手记录从 5-7 次消息减少到 2-3 次。
关键差异总结:1.2 是“先协商、再加密、后验证”;1.3 是“先密钥、再加密、边协商边验证”。
3.2 加密套件的“瘦身”与强迫症
TLS 1.2 时代支持海量密码套件,光是 RSA、DHE、ECDHE、AES-CBC、AES-GCM、CHACHA20 的组合就有几十种。我在项目中遇到过客户端发了一大串密码套件,服务端却选了一个已经废弃的 CBC 模式,结果被中间人攻击搞挂了。
TLS 1.3 直接砍掉了 90% 的套件,只保留 5 种:
- TLS_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
- TLS_AES_128_CCM_SHA256
- TLS_AES_128_CCM_8_SHA256
说白了,1.3 只支持 AEAD 加密模式(Authenticated Encryption with Associated Data),同时强制使用完美的前向安全(PFS)。RSA 密钥交换?再见。CBC 模式?再见。HMAC-SHA1?再见。
个人习惯:我排查握手失败时,第一件事就是看密码套件列表。如果客户端只发了 1.2 的套件(比如 TLS_RSA_WITH_AES_128_CBC_SHA),而服务端只启用了 1.3 套件,那肯定握手失败。很多老旧 SDK 就栽在这上面。
3.3 密钥交换算法:DHE 和 ECDHE 成为唯一选择
1.2 里 RSA 密钥交换是主流,但有个致命伤:服务端私钥一旦泄露,之前所有记录都能被解密。1.3 彻底禁用了 RSA 交换,只允许 DHE(有限域)和 ECDHE(椭圆曲线)。每次连接都生成临时密钥对,握手结束密钥就丢弃。这就是前向安全。
我曾经帮一个金融客户迁移到 1.3,他们服务端配置里还留着旧的 RSA 证书签名但不做密钥交换,结果客户端认证一直失败。排查了两小时,最后发现是 openssl 配置里没开 ECDHE 曲线。
3.4 证书验证的变化
1.2 的证书链是明文发送的,中间人可以抓包看到证书内容。1.3 把 Certificate 消息放到加密层之后——除了 ServerHello 和部分扩展,后续消息全加密。这样即使有人抓包,也看不到你的证书信息,有效防止了证书泄露和中间人篡改。
但这里有个坑:证书签名算法在 1.3 里也必须用支持 PFS 的算法。我见过有人用 SHA1 签名的证书,在 1.3 下直接报错。嗯,SHA1 已经被彻底拉黑了。
| 对比维度 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 握手往返次数 | 2-RTT(完整握手) | 1-RTT / 0-RTT |
| 密码套件数量 | 30+ | 5 |
| 密钥交换 | RSA / DHE / ECDHE | 仅 DHE / ECDHE |
| 前向安全 | 可选(RSA 不支持) | 强制 |
| 加密模式 | AEAD + CBC + 流式等 | 仅 AEAD |
| 证书传输 | 明文 | 加密后传输 |
| 会话恢复 | Session ID / Session Ticket | Pre-shared Key (PSK) + 0-RTT |
| 扩展支持 | 可协商扩展(SNI, ALPN 等) | 扩展列表更严格,未知扩展直接失败 |
3.5 会话恢复机制的演进
1.2 的会话恢复靠 Session ID(服务端保存状态)或 Session Ticket(客户端保存加密票据)。问题在于恢复时仍然需要一次完整的 ServerHello 和证书验证,只是跳过密钥交换。
1.3 引入 Pre-shared Key(PSK)模式。首次握手后,服务端发一个 PSK 标识(由早期密钥派生出的身份标识)。下次客户端可以直接用 PSK 发起 0-RTT 握手,甚至连网络延迟都不等,客户端直接发送加密的应用数据。
避坑指南:我曾经在测试 0-RTT 时,发现客户端发送的 0-RTT 数据被服务端拒绝。原因是 PSK 有效期过期(默认仅 2 小时)。解决办法是缩短或延长 PSK 生命周期,同时服务端要配置 max_early_data_size。否则重放攻击或数据丢失会让你抓狂。
3.6 扩展机制的严格化
1.2 里扩展是可选的,解析遇到不认识的就忽略。1.3 不一样,协议定义了一系列强制扩展(如 supported_versions, key_share, signature_algorithms)。客户端如果缺少某个强制扩展,服务端直接丢回 “unrecognized_name” 或 “illegal_parameter” 告警。
我记得有一次排查一个 Android 客户端连不上服务器,抓包发现 ClientHello 里没有 key_share 扩展。原来那台设备系统自带 OpenSSL 版本太老,根本不支持 TLS 1.3。客户端假装支持 1.3,发了个 1.3 版本号,却没带 key_share,服务端直接断连。
所以调试时,一定要看清 ClientHello 里扩展列表的完整性。Wireshark 的 “TLS” 解析器有详细视图,按 Export Packet Dissections 可以导出扩展明细。
# 用 openssl 工具模拟 1.3 握手,看看扩展
openssl s_client -connect example.com:443 -tls1_3 -state
# 输出中会列出扩展协商结果:
# TLS 1.3 handshake ... Supported Versions: 0x0304
# Key Share: group X25519, curve ...
# Extended master secret: yes
3.7 混合版本环境下的兼容策略
现在很多服务端需要同时支持 1.2 和 1.3。客户端会发一个 supported_versions 扩展,列出自己能支持的版本列表。服务端挑最高的共同版本。如果客户端只支持 1.2,它可能根本不会发 supported_versions(或者只发 0x0303),服务器就降级到 1.2 握手。
这个降级机制有个潜在陷阱:中间人可以把 supported_versions 里的最高版本改成 1.2,强制降级。1.3 协议本身有降级检测(通过特殊的 random 值标记),但前提是两端都实现正确。我在一些老版本 Nginx 上见过降级检测没写全,导致安全审计不通过。
一句话总结版本演进:TLS 1.3 不是 1.2 的补丁,而是从密码套件、握手流程、扩展机制到安全假设的全面重构。调试接口时,版本不匹配往往是“你看不见却最先出问题”的那一环。