TLS握手过程详解
说起TLS握手,很多同学第一反应就是“客户端发个Hello,服务端回个Hello”。嗯,本质上没错,但里面门道不少。我在项目里就踩过坑——有一次证书链没配全,导致握手卡在第三步,排查了大半天。说白了,握手过程就是双方互相确认身份、协商加密参数、建立安全通道。咱们一步步拆开看。
ClientHello:客户端发起请求
握手由客户端主动发起。它发送一个ClientHello消息,里面装着客户端支持的能力列表。
- 支持的TLS版本:比如TLS 1.2、1.3,客户端会把能用的都列上。
- 加密套件列表:一堆密码组合,按优先级排好。
- 随机数:28字节的随机值,后面生成密钥要用。
- 扩展字段:SNI(服务器名称指示)、ALPN(应用层协议协商)等。
我个人的习惯:客户端发送的加密套件列表,首选的尽量放强安全的。我之前见过有人把NULL加密套件排在前面,握手是成功了,但数据是明文传输…安全审计直接红牌。
ServerHello:服务器响应与选择
服务端收到ClientHello后,从客户端提供的列表里挑一个它最满意的组合。然后回复ServerHello。
| ServerHello字段 | 说明 |
|---|---|
| TLS版本 | 最终选定的版本,比如TLS 1.2 |
| 加密套件 | 选中的套件,比如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 |
| 随机数 | 服务端生成的32字节随机值 |
| Session ID | 用于会话复用,如果支持的话 |
证书验证:信任的基石
这一步很关键。服务端把自己的证书链发过来,客户端要验证是否可信。
- 证书链传递:服务端发送从服务器证书到根CA的完整链。
- 有效期检查:证书是否在有效期内。过期?直接断连。
- 域名匹配:证书里的CN或SAN字段,必须与客户端访问的域名一致。
- 签名验证:用上级CA的公钥验证下级证书的数字签名。
- 吊销检查:通过CRL(证书吊销列表)或OCSP(在线证书状态协议)检查证书是否被吊销。
我曾经处理过一个线上事故:某个服务的证书链只发了服务器证书,没有中间CA证书,客户端本地又没缓存,导致握手失败。记住,证书链必须完整,别偷懒。
密钥交换:安全隧道的关键
握手的前三步是握手阶段1,到这里正式开始协商共享密钥。TLS 1.2里主流用的是ECDHE(椭圆曲线Diffie-Hellman临时密钥交换)。
- ServerKeyExchange:服务端发送它的椭圆曲线参数和公钥。
- ClientKeyExchange:客户端发送自己的公钥。
- 计算预主密钥:双方用对方的公钥和自己的私钥,通过ECDH算法计算出相同的预主密钥。
- 派生主密钥:利用预主密钥加上之前交换的随机数,通过PRF(伪随机函数)生成会话密钥。
为什么用ECDHE而不是RSA?说白了,ECDHE支持前向安全性——即使私钥泄露,历史会话也无法被解密。我建议能上ECDHE就上,别用RSA静态密钥交换。
加密通信:数据保护的最后一步
密钥交换完成后,双方发送ChangeCipherSpec消息,告诉对方“我要开始加密了”。然后发送Finished消息,里面是对整个握手过程的MAC值。
- ChangeCipherSpec:一个协议级别的标志,不是握手消息的一部分。
- Finished:用协商好的密钥加密整个握手摘要,双方验证一致,握手才算成功。
- 应用数据:从这开始,所有HTTP请求/响应都经过对称加密传输。
关键点:握手过程中的任何一步失败,连接直接断开。日志里出现"handshake failure"或"alert",大多是这里出问题。我个人排查时,会从证书开始看,然后对比随机数和密钥交换参数。
对了,TLS 1.3的握手更简洁,把ServerHello和Certificate合并了,还移除了静态RSA和CBC模式。但不管哪个版本,这五个大阶段是固定的。理解了这个流程,你再看日志里的握手异常,基本能一眼定位。