3. 完整的TLS握手过程详解
说实话,干了这么多年安全优化,最能看出团队基本功的,就是看他们对TLS握手理解得透不透。我面试时经常问这个问题——不是要你背RFC,而是看你能不能把这条“加密通道”的搭建逻辑说清楚。
好,咱们直接进入正题。一个完整的TLS 1.2握手,总共11步,分四个阶段。你别怕数字,理解了本质,你会发现这就像两个人交换保险柜钥匙,只不过用的是数学。
3.1 握手究竟在干什么?
说白了,就是三件事:
- 身份验证:确认服务器(有时客户端)是它声称的那个。
- 密钥协商:双方面对面定一个只有你们知道的临时密码。
- 算法确认:统一用哪种加密、哪种哈希。
我习惯把TLS握手比作“面对面喊话”和“悄悄传纸条”的组合。前半段喊话是明文,后半段传纸条就开始加密了。
3.2 TLS 1.2 全握手流程
下面这张图是我自己画的标准流程——每个箭头背后都有一组报文交互。
图解重点:注意图中客户端和服务器两侧的“Change Cipher Spec”报文。很多刚入门的同学以为它属于握手协议,其实它是个独立的协议,专门用来“切换密钥状态”。
3.3 流程拆解——每一步都在干什么?
我再给你用文字过一遍,每个阶段都有哪些报文,你对着图看。
阶段一:Hello 协商(第1步)
客户端发 ClientHello,里面包含:
- 最高支持TLS版本(比如1.2)
- 一个32字节的随机数(Client Random)
- 支持的加密套件列表(Cipher Suites)
- Session ID(用于会话复用,以后会细讲)
服务器收到后,回复 ServerHello,带上:
- 选定的TLS版本
- 自己的随机数(Server Random)
- 选定的加密套件
- 还会携带自己的证书链(Certificate)、DH参数(ServerKeyExchange)、最后说一句“我说完了”(ServerHelloDone)。
这里要注意,如果服务器用的是RSA密钥交换,就不会有ServerKeyExchange报文。嗯,这块很多人会搞混。
阶段二:密钥交换(第3步)
客户端验证证书后,取出公钥,生成一个48字节的预主密钥(Pre-Master Secret),用服务器公钥加密后通过 ClientKeyExchange 发送出去。
这时候,双方都能算出主密钥(Master Secret)了。算法是:
Master Secret = PRF(Pre-Master Secret, "master secret", Client Random + Server Random)
PRF是伪随机函数,在TLS 1.2里用的是SHA-256。
阶段三:切换密码状态(第4、5、6步)
客户端先发 ChangeCipherSpec,通知服务器“我接下来要加密通信了”。然后立刻发 Finished——这是第一个加密的报文,里面包含对之前所有握手消息的MAC校验。
服务器同样回复 ChangeCipherSpec + Finished。直到双方都收到并验证对方的Finished消息,握手才算真正完成。
我曾经在线上排查过一次问题:客户端发完Finished后等不到服务器的Finished,查了半天发现服务器端证书链验证超时(有一张中间CA证书没配置)。这种坑,你得做到心中有数。
3.4 TLS 1.3 有什么不同?
你也许好奇,为什么不直接从1.3开始讲?因为现实中80%以上的服务还跑在1.2上(2025年的数据)。但了解1.3能帮你理解为什么1.2需要优化。
| 对比项 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 握手往返次数 | 2 RTT(完整握手) | 1 RTT(完整握手) |
| 密钥交换方式 | RSA / DH (可协商) | 仅 ECDHE(强制前向安全) |
| ServerHello后发送 | Certificate 单独发送 | Certificate 加密后发送(更隐私) |
| 支持的加密套件 | 几十种 | 5种(更简单更安全) |
1.3里把密钥交换和证书验证压缩到一个往返里,而且去掉了RSA这种不支持完美前向安全的算法。说实话,我建议新业务直接上1.3,但如果你要兼容旧设备,1.2还得扛着。
3.5 容易被忽略的两个细节
Tip:证书链验证 — 客户端在收到ServerCertificate后,必须验证证书的有效性(过期、吊销、CA信任链)。这一步如果跳过,中间人攻击就能轻易得手。我见过有人为了省事,在生产环境关闭了证书验证,后果可想而知。
Warning:ChangeCipherSpec 的位置 — 记住,它不属于握手协议(Handshake Protocol),而是 Alert 级别的 ChangeCipherSpec Protocol。发送时不能附带额外数据。有的老旧实现会把它和Finished合并在一个TCP包,导致接收方解析出错。
另外,握手阶段的所有消息都是连续发送的,但接收方必须按序处理。有一次我调试时发现,客户端把ClientHello和ClientKeyExchange塞在同一个包发过去,服务端解析ClientKeyExchange时还没拿到客户端的随机数,直接报错。原因就是没有等ServerHello先到达。所以,别看就是发几个包,顺序乱一次,连接就断了。
3.6 握手开销——你优化的是什么?
完整TLS 1.2握手需要2个网络往返(2-RTT)。如果加上TCP三次握手,总耗时至少3-RTT。以100ms的RTT为例,光握手就要300ms。所以后面要讲的会话复用、OCSP Stapling,核心都是砍掉部分握手步骤,减少网络延迟。
记住这个数字:一次完整TLS 1.2握手,客户端和服务器各做了:1次证书验证、2次公私钥运算、3次HMAC计算。单次连接的开销几乎都花在非对称加密上(RSA或ECDHE)。