第4章:TLS握手流程:双向握手、会话恢复、密钥交换
说实话,我觉得TLS握手是整个协议里最值得花时间琢磨的一块。很多开发者把TLS当成“黑盒子”,配置完证书就完事了。但在做合规审计时,握手过程的每个细节都可能成为安全隐患的突破口。我亲手调过不下百次握手失败的案例,几乎每次都有新发现。
这一章我们就来拆解TLS握手的完整流程。说白了,就是从客户端说“你好”开始,到双方安全地交换数据为止。
4.1 握手到底在干什么?
先问一个问题:为什么需要握手?你想想看,两个人在公共场合第一次见面,想聊点私密话题。你们得先确认身份,约定一个只有你俩知道的暗号,然后才能放心交谈。TLS握手就是这个“确认身份”和“约定暗号”的过程。
具体要干三件事:
- 身份验证 – 证明对方是它声称的那个人(通常由服务器出示证书)
- 协商参数 – 决定用哪个TLS版本、哪套加密套件
- 生成密钥 – 双方各自算出相同的会话密钥,后续通信全程加密
4.2 完整握手流程(一次握手)
先看最常见的“一次完整握手”。这里拿TLS 1.2举例,因为它最典型。TLS 1.3虽然简化了,但理解1.2能帮你打好基础。
我把这个过程分成四个阶段讲:
阶段一:Hello 交互
- ClientHello – 客户端发问候,带着支持的TLS版本列表、加密套件列表、一个随机数(Client Random)
- ServerHello – 服务端从中选定一个版本和一个套件,返回自己的随机数(Server Random)
- Certificate – 服务端把自己的数字证书链发过去(通常是X.509格式)
- ServerHelloDone – 服务端说“我这边发完了,轮到你了”
阶段二:密钥协商
- ClientKeyExchange – 客户端生成一个“预主密钥”,用服务端的公钥加密后发过去
- 双方各自用相同的算法,结合Client Random、Server Random和预主密钥,计算出完全相同的主密钥
- 再从主密钥派生出后续用于对称加密的会话密钥、MAC密钥和IV
这一步是安全的核心。预主密钥只有服务端的私钥能解开。即使有人截获了加密后的数据包,没有私钥也白搭。
阶段三:变更密码规范
- ChangeCipherSpec – 客户端通知服务端:“接下来我要用刚才协商好的密钥加密了”
- Finished – 客户端用新密钥加密一条验证消息(包含之前所有握手消息的哈希值)
- 服务端做同样的事,解密并验证。如果对不上,握手立即失败
阶段四:握手完成
双方各自发送一次ChangeCipherSpec和Finished。全部校验通过后,握手正式结束。后面的HTTP数据全部用会话密钥加密传输。
下面这张图把这个过程完整串起来了:
4.3 双向握手(双向认证)
上面提到的是“单向认证”——只有服务器出示证书。那“双向握手”呢?说白了就是客户端也得把自己的证书亮出来,服务器也去验证一遍。这在企业内部应用或金融场景中很常见。
流程上多了两步:
- 服务端在ServerHello之后,会额外发一个CertificateRequest消息,要求客户端提供证书
- 客户端收到后,发送自己的Certificate(以及对应的CertificateVerify消息,用私钥签名证明自己持有私钥)
不过双向握手也有麻烦的地方。证书管理成本翻倍——你可能要管理几百个客户端的证书。而且每次客户端证书过期,就得重新走一遍申请流程。我在项目里见过因为客户端证书没及时更新,导致整个生产环境全部断连的事故。
4.4 会话恢复
每次新连接都来一次完整握手?性能上扛不住。尤其是移动端场景,网络差、延迟高,完整握手多出来的几个RTT(往返时延)体验极差。
会话恢复就是为了解决这个问题的。简单说就是“复用之前的握手结果”。
TLS提供了两种主流方式:
| 方式 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| Session ID | 服务端保存会话ID和密钥,客户端连接时带上ID直接复用 | 实现简单,兼容性好 | 服务端要存状态,负载均衡场景下容易失效(请求落到不同节点就丢了) |
| Session Ticket | 服务端将会话状态加密成Ticket发给客户端,客户端下次连接时带回来解密复用 | 服务端不需要存状态,水平扩展友好 | 需要服务端维护Ticket密钥,密钥泄露风险值得注意 |
我个人偏向用Session Ticket。那次做完一个百万并发接口的审计,发现服务端Session表里堆了几十万条记录,内存暴涨。后来切到Ticket方案后,服务端完全无状态,内存瞬间降下来了。但要注意——Ticket的加密密钥要定期更换。
4.5 密钥交换:不只是RSA
早期的TLS主要用RSA做密钥交换。但RSA有个硬伤——它不提供前向安全性。什么意思?服务端的私钥如果泄露了,攻击者可以用私钥解密之前所有被截获的预主密钥,进而还原出所有历史会话的密钥。这很要命。
现在的TLS 1.2和1.3推荐用(EC)DHE——这就是所谓的“短暂密钥交换”。
基本原理:
- 客户端和服务端各自生成一对临时的DH密钥对
- 交换各自的DH公钥
- 双方独立计算出一个共享密钥(使用私钥和对方的公钥)
- 即使攻击者拿到长期私钥,也只能验证身份,无法还原出之前的临时DH私钥
简单说,RSA像在公共邮箱里放信,邮箱钥匙丢了,所有信都能被看。DHE像每次通信都换一个新邮箱,邮箱钥匙丢了,也只丢你丢钥匙那一刻的那一封信。
4.6 审计检查清单
做完这一章,你应该清楚握手过程的每一个关键点了。我建议你把这些内容整理成一份检查清单:
| 检查项 | 审计标准 | 常见违规 |
|---|---|---|
| TLS版本 | ≥ TLS 1.2(推荐1.3) | 服务端降级到TLS 1.0/1.1 |
| 证书链完整性 | 必须包含中间证书 | 缺中间证书,手机端、老旧浏览器无法验证 |
| 密钥交换算法 | 仅允许 ECDHE | 启用 RSA 或 DHE 套件 |
| 会话恢复方案 | Session Ticket + 密钥轮换 | 未启用恢复,或Ticket密钥长期不变 |
| Finished校验 | 握手消息MAC校验必须开启 | 忽略校验,可能导致降级攻击 |
下回去审计一个TLS部署,你可以直接拿这张表去对照。少一项,就算不过。