TLS握手协议详解:ClientHello, ServerHello, 证书验证, 密钥交换

要说TLS最核心的部分,那一定是握手。你想想看,握手之前两端什么都不知道——没有密钥、没交换过参数、你想传输数据都不知道怎么加密。所以刚开始交换的那些报文,直接决定了后续会话的安全性。我经历过很多奇怪的问题,最后都回到握手这一步来找根源。真的,搞定握手,你就懂了一半TLS。

ClientHello:客户端的第一声招呼

客户端发起连接后,首先发一条ClientHello。这条消息里它告诉服务器自己支持什么版本、什么密码套件、还有哪些扩展。我当年第一次用Wireshark抓握手包,看到ClientHello里长长一串列表,当时就懵了。但后来发现,其实这里面最重要的字段就几个。

  • TLS版本:客户端最高支持的版本。比如TLS 1.2或1.3。
  • Cipher Suites:按优先级排序的密码套件列表。我建议最少提供10个以上,否则服务端可能选到你不想要的弱套件。
  • Random:32字节随机数,用于后续密钥衍生。注意不是随意填的,必须真随机。
  • Session ID / Session Ticket:用于会话复用。这个后面章节会细讲。
  • Extensions:比如SNI、ALPN、支持的椭圆曲线等。嗯,这里要注意——现在很多网站不支持SNI的客户端直接拒绝。
个人避坑:我曾经遇到一个遗留系统,客户端只发了TLS 1.0的ClientHello。服务端没做降级保护,直接同意了。结果呢?POODLE攻击直接曝露。所以现在我写代码时一定会强制客户端不发送低于TLS 1.2的版本。

ServerHello与Certificate:服务端的回应

服务端收到ClientHello后,会从客户端支持的列表里挑一个版本和一套密码套件,然后发回ServerHello。同时,如果要求双向认证(很少见),服务器也会请求客户端证书。但最常见的场景是服务端带上自己的证书链一起发给客户端。

这里有一个很多人容易忽略的细节:ServerHello中的Random必须独立生成,不能和客户端一样。我遇到过甲方工程师直接复制客户端的Random过来,结果算出来的密钥两边不一致——握手直接断开。

证书链完整性:服务端发送证书时,最好带上完整的中间证书,不要只发叶证书。否则客户端如果缺少中间CA,还得额外下载,增加延迟。我自己的项目里都是配置全链发送。

证书验证:客户端的信任决策

客户端收到证书后,会按以下步骤验证:

  1. 检查有效期:是不是在有效期内?不在?直接拒绝。
  2. 验证签名:用CA的公钥验签。
  3. 检查吊销状态:通过CRL或OCSP。这里是个性能痛点,OCSP stapling能解决,后面章节讲。
  4. 域名匹配:证书里的CN或subjectAltName必须匹配客户端访问的域名。

我当初实习的时候,有个同事把证书挂到了错误的域名上,结果花了三小时才发现。从那以后我每次部署证书前都会手打一遍 openssl x509 -in cert.pem -text -noout 看域名字段。

密钥交换:协商最终的对称密钥

证书验证通过后,开始真正的密钥交换。具体过程取决于ServerHello里选中的密码套件。我整理了一个常用对比表:

密码套件类型 密钥交换方式 前向安全性 常见场景
RSA密钥交换 客户端生成pre-master secret,用服务器公钥加密传输 ❌ 无 老旧系统(不建议使用)
DHE_RSA 服务端生成DH参数,用RSA签名;客户端生成自己的DH值 ✅ 有 兼容性优先的现代场景
ECDHE_RSA/ECDSA 类似DHE但使用椭圆曲线,性能更好 ✅ 有 主流推荐(我基本只用这个)

说白了,现在的握手我几乎只见ECDHE。原因很简单:前向安全、密钥协商速度还快。我记得有一次给一个金融客户调优,把RSA换成了ECDHE_X25519,握手时间降了30%。

最后一步:双方用pre-master secret和两个Random通过PRF(伪随机函数)计算出主密钥(master secret)。然后各自生成加密密钥、MAC密钥等。完成后发送ChangeCipherSpec,并验证Finished消息。如果Finished里的校验值能对上,握手成功。否则,哪个环节错了,连接就断。

重要提醒:千万不要直接把pre-master secret当作加密密钥使用。必须经过密钥衍生步骤。曾经有早期实现这么干,导致hello漏洞。TLS的设计中每一层都是经过精心计算的,别自作聪明。

核心流程一览

下面这张SVG图展示了TLS 1.2完整握手的主要消息流(省略了重协商等复杂情况)。你可以对照着理解每一步在做什么。

TLS 1.2 完整握手流程图 客户端 (Client) 服务端 (Server) ① ClientHello 选择参数 ② ServerHello + Certificate + ServerHelloDone 验证证书链 ③ ClientKeyExchange(pre-master secret) 计算 Master Secret ④ ChangeCipherSpec + Finished ⑤ ChangeCipherSpec + Finished 握手完成,开始加密通信

你看,整个握手本质上就是客户端和服务端互相确认身份、协商参数、生成共享密钥的过程。每一步都有自己的使命,缺一不可。理解这些细节,对你后续做优化会非常有帮助——比如你知道哪个环节最耗时,就可以针对它下手。

核心总结:ClientHello决定能力,ServerHello决定策略,证书决定信任,密钥交换决定机密性。记住这四句话,你就抓住了握手的脉络。