2. TLS 握手全流程详解
TLS 握手,说白了就是客户端和服务器在正式通信前互相“验明正身”并商量好加密参数的环节。我入行那会儿觉得这步就是走个过场,直到在抓包时发现有人伪造服务端证书 — 嗯,从那以后我再也不敢轻视握手了。
2.1 ClientHello — 客户端发起招呼
握手的第一步,客户端发送 ClientHello 消息。里面包含了什么?
- 版本号:比如 TLS 1.2、TLS 1.3
- 随机数:32 字节,用于后续密钥生成
- 会话 ID:如果之前有会话可以复用
- 密码套件列表:客户端支持的加密算法组合
- 扩展字段:SNI、ALPN、Key Share(TLS 1.3)等
2.2 ServerHello — 服务器回应选择
服务器收到 ClientHello 后,从中挑选一套双方都支持的参数,发回 ServerHello。内容类似:
- 最终选用的协议版本
- 服务端随机数
- 选定的密码套件
- 可选的压缩方法(实际很少用)
这里有个坑:如果服务器选择的版本比客户端低,可能是中间人降级攻击。我记得有一次排查延迟问题,抓包发现服务器莫名其妙选了 TLS 1.0,后来发现是负载均衡器配置错误。
2.3 证书交换 — 身份证明
Certificate 消息:服务器把自己的证书链发给客户端,有时也会请求客户端证书(双向认证)。证书里包含公钥、签发者、有效期等信息。
我习惯用 OpenSSL 检查证书链完整性:
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | openssl x509 -text -noout
如果证书链不完整或自签名,客户端通常会报错。但很多恶意软件会忽略验证,直接信任任意证书。这就是检测异常的好时机。
2.4 密钥协商 — 悄悄生成共同密钥
证书验证通过后,客户端和服务器需要协商出一个对称密钥。常见方式:
| 方式 | 说明 | 典型场景 |
|---|---|---|
| RSA 密钥交换 | 客户端用服务器公钥加密预主密钥,服务器私钥解密 | TLS 1.2 及之前,但不提供前向安全 |
| Diffie-Hellman (DHE/ECDHE) | 双方交换 DH 参数,各自计算共享密钥 | 现代 TLS 推荐,提供前向安全 |
我记得项目经理曾问:为什么 ECDHE 比 RSA 慢?其实只是握手阶段多算两次椭圆曲线乘法,对整个连接的影响几乎可以忽略,换来的是每次会话密钥独立,窃听后也无法解密之前的数据。
客户端发送 ClientKeyExchange(携带 DH 公钥或加密的预主密钥),然后双方各自计算主密钥,再派生出会话密钥。
2.5 Finished 消息 — 握手完整性校验
双方都发送 ChangeCipherSpec(通知后续使用新密钥),紧接着发送 Finished。Finished 是对整个握手消息的 HMAC 摘要,用协商的密钥加密。如果双方校验一致,握手成功;否则连接断开。
这是一个强有力的检测点 — 如果 Finished 消息验证失败,说明握手过程中的某个消息被修改过。我曾经在渗透测试中伪造过 ClientHello 的扩展字段,结果服务端 Finished 直接拒绝,让我花了半天才定位到问题。
2.6 完整握手流程
下面这张图展示了 TLS 1.2 全握手的过程(忽略 Session Ticket 等优化细节):
2.7 异常检测切入点总结
我把常见的异常行为整理成了一张表,方便你在抓包时快速对照:
| 阶段 | 正常表现 | 异常信号 |
|---|---|---|
| ClientHello | 密码套件列表约 10-30 个,版本号合理 | 列表仅含 1-2 个弱套件,或版本号过低 |
| ServerHello | 选择主流套件,版本匹配 | 选择不常见套件,或版本低于客户端 |
| Certificate | 证书链完整,有效期正常 | 自签名、过期、域名不匹配、链不完整 |
| Key Exchange | 预主密钥或 DH 参数符合协议规范 | 长度异常、值重复、使用非标准的椭圆曲线 |
| Finished | 双方校验通过,握手立即完成 | 校验失败、Finished 消息缺失或乱序 |
学会了握手细节,下一层就是把这些异常行为自动化检测。当然,那是后面课程的内容了。如果你现在就想动手,可以先装个 Wireshark,抓一次正常的 HTTPS 连接,对照上面的表逐条验证。我自己就是这么练出来的。