TLS/SSL 握手协议详解(四次握手)

HTTPS 的核心,其实是「HTTP over TLS」。咱们今天要聊的,就是这个 TLS 握手的完整过程。很多人觉得握手就是三次,其实在 TLS 1.2 及更早版本里,我们常说的是四次握手,或者叫「完全握手」。

我个人习惯把握手看作是在安全通信开始前,双方先互相认识、并且约定暗号的过程。你想想看,客户端和服务器要传敏感数据前,总得先确认对方身份、商量好怎么加密吧?嗯,这个过程,就是 TLS 握手要干的活。

一、握手协议的核心目标

握手阶段要解决三个问题:

  1. 身份认证:确认对方的真实身份,防止中间人冒充。
  2. 密钥协商:安全地生成一个只有双方知道的对称密钥,用于后续数据传输的加密。
  3. 参数协商:确定使用哪些加密算法、协议版本等。

一个很形象的比喻:这就是双方在互相对暗号。握手成功,后面就可以安心的脱裤子(传输内容加密)了。

二、四次握手流程详解

为了讲清楚,我把流程画出来。这张图是做嵌入式水印设计时我反复要看的东西——你得知道哪个时机点最适合嵌入。

TLS 1.2 完全握手流程图(四次握手) 客户端 Client 服务器 Server ① ClientHello (1) ② ServerHello + 证书 (2) ③ 密钥交换 + 加密切换 + 完成 (3) ④ 加密切换 + 完成 (4) 👋 握手完成 → 加密数据传输 说明 常见的“四次握手”指:1→2→3→4 四个大步骤。其中③④包含多个子消息。 客户端和服务器各自发送 Finished 消息以确保握手完整、无损、未被篡改。

这张图里我把握手浓缩为 4 个关键步骤。但实际上,每个步骤内部还藏着多个子消息。以前我接手的性能调优项目里,就有人把服务端证书链过大导致握手耗时长的坑踩了好几次。嗯,下文我会把这些坑拆开揉碎了讲。

三、步步详解握手过程

第一步:ClientHello

客户端主动发起,发送一些基本参数给服务端:

  • Client Version:客户端支持的最高 TLS 版本。
  • Client Random:28字节随机数,用于后续密钥生成。说白了就是「我这边先丢个随机种子给你」。
  • Session ID:用于会话恢复。如果之前有握手过,可以带上上次的 session id,尝试快速恢复。
  • Cipher Suites:客户端支持的密码套件清单(比如 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)。
  • Extensions:扩展字段,比如 SNI(Server Name Indication)、ALPN 等。

我曾见过不少客户端在 SNI 扩展里不填写域名,导致服务器无法返回正确的证书——然后直接抛出证书警告。

第二步:ServerHello + 服务器数字证书 + ServerHelloDone

服务端收到 ClientHello 后进行决策,然后回复三个重要信息:

  1. ServerHello:返回服务端选择的 TLS 版本、密码套件(取交集)、服务端随机数。
  2. Certificate:服务端将数字证书(X.509 格式)发给客户端。
  3. ServerHelloDone:通知客户端「我发完了,你继续。」

曾经有一个部署事故,是因为服务器证书链没包含中间 CA,仅发了叶子证书。客户端收到后无法验证到受信根,握手直接中断。所以,证书链一定要完整交付。

第三步:ClientKeyExchange + ChangeCipherSpec + Finished

这一步客户端要确认对方的身份——验证服务器证书的有效性。验证通过后,继续发起:

  • ClientKeyExchange:客户端生成 Pre-Master Secret(预主密钥),用服务端的公钥加密后传过去。
  • ChangeCipherSpec:通知服务端「我要切换加密模式了,接下来发的内容都是加密的」。
  • Finished:客户端发送一个加密的 Finished 消息,内容是之前所有握手消息的摘要。

很多人会混淆 Pre-Master Secret 和 Master Secret。这里我直接给公式:

Master Secret = PRF(Pre-Master Secret, "master secret", ClientRandom + ServerRandom)[0..47]

为什么搞得这么复杂?说白了就是让最终的对称密钥不依赖单一随机源。既能防重放,又能及时失效。

第四步:ChangeCipherSpec + Finished

服务端收到客户端加密的 Finished 后,用自己的私钥解密得到 Pre-Master Secret。接着,服务端也:

  • ChangeCipherSpec:通知客户端「我也要加密通信了」。
  • Finished:发送加密的 Finished 消息。

客户端收到后验证消息完整性。如果验证通过,握手就结束了。之后,双方就使用协商好的对称密钥进行加密传输。

你想想看,四次握手中最脆弱的环节是哪?嗯,是第三步——公钥加密 Pre-Master Secret 的时候。如果服务端私钥泄露,那整个加密形同虚设,所以密钥管理要上硬件。

四、核心知识点对比

特性 对称加密 非对称加密
握手阶段使用 不直接使用(用于后续传输) 用于 Pre-Master Secret 传输
性能 快,适合大块数据 慢,约慢 100-1000 倍
密钥管理 需提前共享密钥 无需共享,公钥公开

那为什么不全程用非对称?很简单:对于一次性握手几个字节的密钥还可以,要是每次视频流都做公钥运算,服务器直接烧掉。以前我压测时就见过,一台普通服务器纯非对称握手只能扛 300 QPS,换成对称后直接翻十几倍。

五、实际部署中的避坑指南

我自己总结了一个常用密码套件排序建议:

  • ECDHE + RSA + AES-128-GCM + SHA256(首选)
  • ECDHE + ECDSA + AES-128-GCM + SHA256(有自带证书链时)
  • 避免使用 CBC 模式的套件,TLS 1.2 后已不建议。

关于服务端证书:

  • 证书有效期不要超过 398 天(Apple 和 Google 都已经限制)
  • 记得启用 OCSP Stapling,否则每次连接客户端都可能去 CA 查询证书状态,增加握手延迟。
  • ECDSA 证书比 RSA 证书短、计算也更快,但兼容性稍差。

我还记得有一次,项目里有人在服务端配置了 TLS 1.0,结果 APP 客户端强行要求 TLS 1.2,握手直接降级失败。所以,服务端至少要支持 TLS 1.2 以上。

六、知识点清单:四次握手小结

  • 客户端随机数 + 服务端随机数 + Pre-Master Secret → 对称密钥
  • Finished 消息:双向握手完整性校验,缺一不可
  • ChangeCipherSpec:只是一个「我要切换了」的信号,本身不承载密钥
  • Session ID / Session Ticket:用于简化后续握手(这篇暂时不讲)
  • 证书链:不能缩短,必须包含根 CA 到叶子证书的完整链条

搞明白这四次握手,以后我们在做 HTTPS 水印嵌入的时候,你就知道该在哪一步动手了。说白了,握手的每个阶段都有 payload 或者扩展字段,这些就是我们的切入点。