4. TLS握手协议全流程(TLS 1.2)

TLS 1.2 的握手协议是整条安全通信链路的“建链阶段”,它要完成四件事:协商密码套件、验证服务器身份、交换密钥材料、确认双方握手记录未被篡改。理解握手的关键不是背消息名,而是看清每条消息“谁发给谁、携带什么、为什么必须在这个位置出现”。本节以一次完整的 TLS 1.2 握手为主线,结合 openssl s_client 的实际输出逐步跟踪。

握手消息的顺序与角色

一次典型的 TLS 1.2 完整握手(以 ECDHE-RSA 密钥交换为例)消息顺序如下:

  1. ClientHello(客户端→服务器):携带客户端支持的 TLS 版本、随机数 client_random(32 字节)、会话 ID、密码套件列表、压缩方法、扩展(如 SNI、supported_groups、signature_algorithms)。
  2. ServerHello(服务器→客户端):选定 TLS 版本、server_random、会话 ID、最终选定的单个密码套件与压缩方法。
  3. Certificate(服务器→客户端):发送服务器证书链,客户端据此验证服务器身份并取出公钥。
  4. ServerKeyExchange(服务器→客户端):仅在使用 ECDHE/DHE 等前向安全密钥交换时出现,携带临时公钥及对握手参数的签名。
  5. ServerHelloDone(服务器→客户端):表示服务器侧握手消息发送完毕。
  6. ClientKeyExchange(客户端→服务器):客户端生成临时公钥并发送,双方据此各自计算出预主密钥 pre_master_secret。
  7. ChangeCipherSpec(双向各一次):通知对端“后续消息开始使用协商好的密钥加密”。
  8. Finished(双向各一次):携带基于主密钥和全部握手消息计算的校验值,验证握手完整性。

其中 client_random 与 server_random 是公开的,但两者与 pre_master_secret 一起通过 PRF 派生出 master_secret,再派生出会话密钥、MAC 密钥和初始向量。这正是“随机数公开但密钥保密”的设计要点。

客户端 Client 服务器 Server ① ClientHello version · client_random · cipher_suites · SNI ② ServerHello version · server_random · 选定 cipher_suite ③ Certificate 服务器证书链(含公钥) ④ ServerKeyExchange ECDHE 临时公钥 + 签名 ⑤ ServerHelloDone ⑥ ClientKeyExchange 客户端 ECDHE 临时公钥 ⑦ ChangeCipherSpec ⑧ Finished(加密) ⑨ ChangeCipherSpec + Finished(加密)

用 OpenSSL s_client 逐步跟踪

最直接的观察方式是使用 openssl s_client。它默认会打印握手消息的十六进制与解析结果,配合 -msg 可看到每条消息的类型与长度,配合 -state 可看到握手状态机推进过程。

openssl s_client -connect example.com:443 -tls1_2 -servername example.com -msg -state

输出中会依次出现类似片段:

>>> TLS 1.2, RecordHeader [length 0005]
    16 03 01 ...
>>> TLS 1.2, Handshake [length 00c4], ClientHello
    01 00 00 c0 03 03 ...
<<< TLS 1.2, Handshake [length 0051], ServerHello
    02 00 00 4d 03 03 ...
<<< TLS 1.2, Handshake [length 0bb8], Certificate
    0b 00 0b b4 ...
<<< TLS 1.2, Handshake [length 014d], ServerKeyExchange
    0c 00 01 49 ...
<<< TLS 1.2, Handshake [length 0004], ServerHelloDone
    0e 00 00 00
>>> TLS 1.2, Handshake [length 0046], ClientKeyExchange
    10 00 00 42 ...
>>> TLS 1.2, ChangeCipherSpec [length 0001]
    01
>>> TLS 1.2, Handshake [length 0040], Finished
    14 00 00 0c ...

这里每条消息开头的单字节就是握手类型:01 是 ClientHello,02 是 ServerHello,0b 是 Certificate,0c 是 ServerKeyExchange,0e 是 ServerHelloDone,10 是 ClientKeyExchange,14 是 Finished。ChangeCipherSpec 属于独立的记录层协议,类型值为 01,与握手消息不在同一编号空间。

关键字段的含义与验证点

在 ClientHello 中,client_random 是客户端生成的 32 字节随机数,前 4 字节在 TLS 1.2 中通常为时间戳,后 28 字节为随机值。密码套件列表按客户端偏好排序,服务器可从中选择。SNI 扩展用于告知服务器客户端想访问的主机名,使同一 IP 能承载多个证书。

ServerHello 中服务器只返回一个选定的密码套件,例如 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。这个名称本身就说明了密钥交换算法(ECDHE)、认证算法(RSA)、对称加密算法(AES-128-GCM)和 PRF 哈希(SHA256)。

Certificate 消息携带的是证书链,客户端需要逐级验证签名、有效期、域名匹配和吊销状态。ServerKeyExchange 中的临时公钥必须由服务器用证书私钥签名,客户端用证书公钥验签,从而防止中间人替换临时公钥。

ClientKeyExchange 发送客户端临时公钥后,双方各自用“自己的私钥 + 对方公钥”计算出相同的共享秘密,再经 PRF 得到 pre_master_secret 和 master_secret。Finished 消息则是对之前所有握手消息的哈希做 PRF 计算,任何一条消息被篡改都会导致校验失败。

在 s_client 交互模式下,可以输入 R 重新协商,输入 Q 退出。若想只看握手摘要,可加 -brief;若想验证证书链,可加 -verify_return_error 让验证失败直接中断。

一次完整握手的观察要点

实际跟踪时,建议按以下顺序核对:第一,ClientHello 中的 SNI 是否与目标域名一致;第二,ServerHello 选定的密码套件是否在客户端列表中;第三,Certificate 链是否完整、是否包含中间 CA;第四,ServerKeyExchange 是否存在(ECDHE 必须有);第五,ChangeCipherSpec 之后的消息是否已加密,Finished 是否双向都出现。若服务器要求客户端证书,还会在 ServerHelloDone 后出现 CertificateRequest,客户端需回应 Certificate 与 CertificateVerify。

掌握这条消息链后,再遇到握手失败时就能快速定位:是版本不匹配、密码套件无交集、证书验证失败,还是签名算法不支持。OpenSSL 的输出会直接给出 alert 类型,例如 handshake_failure、bad_certificate 或 unknown_ca,这些告警正是排查的起点。