4. TLS 1.2握手详解:ClientHello消息结构、ServerHello与密码套件协商、服务器证书与CertificateRequest

好,咱们正式开始啃TLS 1.2握手协议。这一章是整个课程的核心地基。你想想看,客户端和服务器第一次见面,怎么互相确认身份?怎么商量用什么加密算法?这些全在握手阶段搞定。

我个人习惯把TLS握手比作一场「加密相亲」——双方先亮出自己会什么(Hello),然后挑一个双方都满意的方案(密码套件协商),最后出示身份证(证书)。今天我们就按这个流程,把每一步拆开来看。

核心要点:TLS 1.2握手共需2个RTT(往返时间),其中ClientHello和ServerHello是第一步,证书交换是第二步。理解这两个阶段,你就掌握了握手的骨架。

4.1 ClientHello:客户端的第一声招呼

握手一开始,客户端会发送一个ClientHello消息。说白了,就是告诉服务器:「我来了,我支持这些功能,你看哪个合适?」

我截取过一个真实的ClientHello包,结构大致如下:

Handshake Protocol: Client Hello
    Version: TLS 1.0 (0x0301)  ← 注意这里
    Random: 1c 9b 3a 8f ... (32字节)
    Session ID Length: 32
    Session ID: a3 7f ...
    Cipher Suites Length: 28
    Cipher Suites: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
                   TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
                   ...
    Compression Methods Length: 1
    Compression Methods: null (0x00)
    Extensions Length: 217
    Extensions: ...

这里有个坑,我刚开始抓包时经常被搞晕——Version字段写的是TLS 1.0 (0x0301),但客户端实际支持的是TLS 1.2。为什么?因为TLS 1.2的ClientHello里,Version字段固定填0x0301(即TLS 1.0),真正的版本号在Supported Versions扩展里声明。这是为了兼容老服务器。

避坑指南:我曾经在排查一个握手失败问题时,看到ClientHello的Version是0x0301,就以为客户端只支持TLS 1.0。折腾了半天才发现,真正的版本在扩展里。记住:永远不要只看Version字段,要看Supported Versions扩展。

ClientHello里最重要的几个字段:

  • Random:32字节随机数,用于后续生成会话密钥。每次握手都不一样。
  • Cipher Suites:客户端支持的密码套件列表,按优先级排序。服务器会从中选一个。
  • Extensions:扩展区域,承载了SNI(服务器名称指示)、ALPN(应用层协议协商)、Supported Groups(支持的椭圆曲线)等关键信息。

4.2 ServerHello与密码套件协商

服务器收到ClientHello后,会回复一个ServerHello。这个包的结构和ClientHello很像,但内容更精简——服务器只需要告诉客户端:「我选了这个版本、这个密码套件、这个随机数。」

密码套件协商是握手中最关键的一步。服务器从客户端提供的列表中,挑一个它自己也支持的。如果一个都不匹配,握手直接失败。

我见过一个真实案例:客户端只支持TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,但服务器只配置了TLS_RSA_WITH_AES_256_CBC_SHA。结果就是握手失败,浏览器报ERR_SSL_VERSION_OR_CIPHER_MISMATCH。排查起来其实很简单——抓包一看,ServerHello里直接返回了Alert。

密码套件示例 密钥交换 认证 加密 MAC/PRF
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ECDHE RSA AES-128-GCM SHA256
TLS_RSA_WITH_AES_256_CBC_SHA RSA RSA AES-256-CBC SHA1
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 DHE RSA AES-128-CBC SHA256

你看这个表格,密码套件名字里其实包含了所有信息。比如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:

  • ECDHE:使用椭圆曲线Diffie-Hellman密钥交换(前向安全)
  • RSA:使用RSA证书进行身份认证
  • AES-128-GCM:使用AES-128-GCM进行对称加密
  • SHA256:使用SHA256作为PRF(伪随机函数)

个人经验:我建议你在抓包时,重点关注密码套件里的密钥交换算法。如果是RSA密钥交换(即套件名里只有RSA,没有DHE或ECDHE),说明握手不支持前向安全性。现在的主流实践是优先使用ECDHE。

4.3 服务器证书与CertificateRequest

ServerHello之后,服务器会发送Certificate消息,把自己的证书链发给客户端。证书里包含了服务器的公钥、颁发者信息、有效期等。

证书的结构是X.509格式,抓包看的话,主要关注这几个字段:

  • Subject:证书持有者,比如 CN=*.example.com
  • Issuer:颁发者,比如 CN=DigiCert Global CA
  • Validity:有效期,注意检查是否过期
  • Subject Public Key Info:公钥信息,包括算法(RSA/ECC)和密钥长度

如果服务器要求客户端也出示证书(比如双向认证场景),它会额外发送一个CertificateRequest消息。这个消息里包含了服务器可接受的CA列表和证书类型。

我记得有一次帮客户排查一个金融接口的问题,他们的API要求双向TLS认证。客户端一直报错,我抓包一看——服务器发了CertificateRequest,但客户端根本没发证书。原因是客户端代码里没配置客户端证书。加上之后,握手就通了。

关键点:CertificateRequest是可选的。绝大多数Web场景(比如HTTPS)都是单向认证——服务器发证书,客户端验证。只有银行、企业内部系统等场景才会用双向认证。

服务器发完Certificate(和可选的CertificateRequest)后,还会发一个ServerHelloDone消息。这个包没有内容,就是一个标记——告诉客户端:「我发完了,该你了。」

嗯,到这里,握手的第一阶段就结束了。客户端收到了服务器的证书、选好的密码套件、以及随机数。接下来,客户端要验证证书、生成预主密钥、然后发送自己的密钥交换信息。这些我们下一章再细聊。

TLS 1.2 握手第一阶段流程图 客户端 服务器 ① ClientHello 版本、随机数、密码套件列表、扩展 ② ServerHello 选中的版本、密码套件、随机数 ③ Certificate 服务器证书链(X.509) ④ CertificateRequest(可选) 双向认证时使用 ⑤ ServerHelloDone 标记:服务器消息结束 💡 第一阶段共1个RTT:客户端发送ClientHello → 服务器回复ServerHello + Certificate + ServerHelloDone 如果服务器要求客户端证书,会额外发送CertificateRequest(虚线箭头)

抓包小技巧:用Wireshark过滤TLS握手时,可以直接用 tls.handshake.type == 11 过滤Certificate消息(type 11),用 tls.handshake.type == 13 过滤CertificateRequest(type 13)。这样能快速定位证书相关的问题。