TLS 握手协议详解
TLS 握手,说白了就是客户端和服务器在正式传输数据前,先互相打个招呼、亮个身份、商量好怎么加密的一个流程。很多开发者觉得这步就是走个过场,但其实它直接决定了整个会话的安全基础。我见过不少安全事件,根源就是握手阶段没处理好。
握手到底在做什么?
咱们可以把它想象成两个陌生人第一次见面:
- 先打招呼:Client Hello / Server Hello,交换随机数、支持的密码套件、TLS 版本。
- 亮证件:服务器发送证书,证明自己是它声称的那个人。
- 对暗号:客户端验证证书,然后双方用自己的私钥 / 公钥完成密钥交换,生成一个只有他俩知道的会话密钥。
- 最后确认:双方互发一个“Finished”消息,握手结束,之后的数据全部用会话密钥加密传输。
这里有个点容易忽略:TLS 1.3 和 TLS 1.2 的握手流程差别很大。1.3 把很多步骤合并了,少了一次往返(1-RTT),甚至支持 0-RTT。我建议审计的时候先确认实际用的是哪个版本,再对照标准流程检查。
完整握手流程(TLS 1.2 为例)
下面这张流程图,是我从一个真实项目中总结出来的。你把它套到包里,基本不会走偏。
关键步骤拆解
1. ClientHello 和 ServerHello
客户端把支持的密码套件列表扔给服务端,服务端挑一个。这个挑选过程,我曾经遇到过一个坑:服务端没有按照客户端首选项选,而是选了一堆过时的低强度套件,结果被扫出弱加密漏洞。
审计时一定要抓包对比:服务端选择的套件是不是在客户端列表里?是不是满足当前安全策略的最低强度要求?
2. 证书验证
客户端收到服务器证书后,会做几件事:
- 检查证书链是否完整、是否受信任
- 检查证书有效期(容易被忽略的是,有些服务器用已过期的证书依然能握手成功,只是用户端会弹出警告)
- 检查证书 CN 或 SAN 是否匹配访问的域名
- 检查证书吊销状态(OCSP / CRL)
3. 密钥交换
常见方式有两种:RSA 密钥交换和 (EC)DHE 密钥交换。RSA 方式里,客户端直接用服务器公钥加密 Pre‑Master Secret。这种方式简单,但有个致命问题——没有前向安全性:一旦服务器私钥泄露,之前所有会话都能被解密。
所以我个人习惯,在审计报告里强烈推荐 (EC)DHE。TLS 1.3 已经彻底废弃了 RSA 密钥交换,但很多存量 TLS 1.2 还在用。
| 密钥交换算法 | 前向安全性 | TLS 1.3 支持 | 审计建议 |
|---|---|---|---|
| RSA | 否 | 否 | 禁止使用 |
| DHE | 是 | 否(但在 1.2 中可用) | 允许,但需要参数 ≥ 2048 位 |
| ECDHE | 是 | 是 | 推荐使用 |
4. Finished 消息与验证
握手最后一步,双方用协商好的会话密钥加密一条 Finished 消息发给对方。这条消息的内容是基于此前所有握手消息的哈希值。如果对方解不开或者哈希不匹配,握手就会失败。
嗯,这里要注意:Finished 消息是第一条被加密传输的数据。我之前审计过一个 SDK,它在 Finished 之前就开始发应用数据了,这是个严重缺陷,攻击者完全可以注入伪造数据。
TLS 1.3 的简化
TLS 1.3 把上面的握手从 2-RTT 压缩到 1-RTT(甚至 0-RTT),主要是靠“提前给出密钥共享参数”。流程变成了:
- 客户端直接发送 ClientHello + 密钥交换的 share
- 服务端回复 ServerHello + 自己的 share + 证书 + 直接进入加密状态
- 双方几乎同时算出会话密钥,加密 Finished
审计时要注意:TLS 1.3 不支持很多老密码套件,比如 RSA 密钥交换、CBC 模式等,如果应用层代码里硬编码了某些套件,可能导致握手失败。我亲手帮一个客户查过一个线上故障,就是因为他们服务端配置了 TLS 1.3 但密码套件列表里还留着只有 1.2 才用的套件,结果客户端选择困难,干脆降级成 1.2 了。这不算是漏洞,但性能差了不少。
- 版本协商:是否允许 TLS 1.0/1.1?通常应该禁用。
- 证书链:是否完整、是否信任、是否吊销。
- 密钥交换:是否使用 (EC)DHE,参数强度是否够。
- 密码套件:是否包含弱套件(如 RC4、CBC-MAC、3DES、export 级)?
- Finished 验证:报文顺序和加密时机是否合规。