TLS 1.2 握手过程详解

说实话,TLS 1.2 的握手过程,是所有安全工程师的必修课。你搞不懂它,后面做下线、做迁移,心里就没底。我记得刚接触这行的时候,光看 RFC 文档看得头大,后来亲手抓了几次包才真正明白。

简单来说,TLS 1.2 握手是客户端和服务器之间的一轮"默契配合"。双方要协商加密算法、交换密钥、验证身份,最后才能开始安全通信。整个过程通常需要 2 个 RTT(网络往返),比后来的 TLS 1.3 慢一些,但胜在成熟、生态好。

TLS 1.2 完整握手流程 客户端 服务器 ClientHello ServerHello Certificate ServerKeyExchange ServerHelloDone ClientKeyExchange ChangeCipherSpec Finished (客户端) ChangeCipherSpec Finished (服务器) 握手过程共经历 4 次消息交换(2 RTT)

握手流程拆解

整个握手分成四个阶段。我习惯把它记成"打招呼→亮身份→交换密钥→确认收工"。下面逐个拆开看。

第一步:ClientHello

客户端发起问候。包里装着客户端支持的 TLS 版本、加密套件列表、随机数(Client Random),还有可选的扩展(如 SNI、ALPN)。这就像你去酒店前台说"我想住店,这是我的偏好清单"。

抓包小技巧: 用 Wireshark 过滤 tls.handshake.type == 1 就能定位 ClientHello。我当年排查一个握手失败问题时,发现客户端居然把 TLS 1.2 排在了 TLS 1.0 后面——这是个经典坑。

第二步:ServerHello + 证书

服务器收到 ClientHello 后,从客户端支持的列表中挑一套加密套件,然后回消息。消息里包含:

  • ServerHello —— 选中的版本、加密套件、服务器随机数
  • Certificate —— 服务器证书链(至少包含叶子证书)
  • ServerKeyExchange —— 携带密钥交换参数(比如 DH 公钥、签名)
  • ServerHelloDone —— 表示服务器本轮发完了

注意,并不是所有握手都有 ServerKeyExchange。如果用的是 RSA 密钥交换(现在已废弃),这一步可以省略。我以前踩过这个坑,以为所有握手的消息顺序都一样,结果调试了半天。

第三步:客户端密钥交换

客户端验证证书没问题后,生成 Pre-Master Secret(预主密钥),然后用服务器的公钥加密发过去。这一步非常关键:

// 伪代码示意
pre_master_secret = 48 字节随机数(协议版本 + 46 字节随机)
encrypted_pre_master = RSA_encrypt(server_cert_public_key, pre_master_secret)

发送完后,客户端也会发送 ChangeCipherSpec 切换加密环境,再发一个 Finished 消息(里面是对整个握手消息的 HMAC 验证)。两边各自计算相同的 Master Secret,后续通信的对称密钥就靠它了。

关键公式:

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

PRF 用的是 SHA-256(TLS 1.2 默认)或其它协商的哈希算法。

第四步:服务器确认

服务器收到客户端的 Finished 后,也用同样的方式切换加密套件,发 ChangeCipherSpec 和自己的 Finished 消息。如果双方验证 HMAC 通过,握手完成。这时就可以开始真正的应用数据传输了。

握手中的核心要素对比

消息 方向 作用 是否必须
ClientHello C→S 宣布能力、随机数 是
ServerHello S→C 选定参数、服务器随机数 是
Certificate S→C 服务器身份证明 通常必须(匿名套件除外)
ServerKeyExchange S→C 携带 DH 参数或 EC 点 视密钥交换算法而定
ClientKeyExchange C→S 传输预主密钥 是
ChangeCipherSpec 双向 通知对方切换加密 是
Finished 双向 验证握手完整性 是

需要注意的细节

我曾经踩过的一个坑: 有些老旧客户端只支持 TLS 1.0 和 TLS 1.2,但 ServerHello 里如果返回了 TLS 1.1,客户端会直接断连。原因是客户端认为服务器不匹配自己的版本列表。所以服务器最好严格回选客户端列表里的版本,不要自作主张。

另外,证书验证这一步很关键。我自己的习惯是在客户端开启 SSL_VERIFY_PEER,并且校验证书 CN 或 SAN 与域名是否匹配。如果你只检查证书链合法但不管域名,中间人攻击照样能得手。

握手的一个完整例子

以 ECDHE-RSA-AES128-GCM-SHA256 这套件为例,流程如下:

  1. 客户端发 ClientHello,支持 ECDHE、AES-128-GCM、SHA-256
  2. 服务器回复 ServerHello 选中该套件,并附带 ECDHE 临时公钥(用 RSA 签名)
  3. 客户端验证签名,用 ECDHE 计算共享密钥,生成 Pre-Master Secret
  4. 双方通过 PRF 导出 Master Secret,继而派生出加密密钥和 HMAC 密钥
  5. 切换加密后互发 Finished

嗯,到这里握手基本清楚了。说白了,TLS 1.2 的握手虽然比 1.3 繁琐,但你掌握了这个结构,后面理解 1.3 的 0-RTT 和兼容性设计就会轻松很多。

调试建议: 用 openssl s_client -connect host:port -tls1_2 -msg 可以看到握手原始消息内容。我排查证书链问题时经常这么干。