TLS 握手核心流程:四次握手概览
好,咱们正式开始啃 TLS 握手这块硬骨头。很多人觉得 TLS 握手很神秘,说白了,它就是客户端和服务器在正式传输数据之前,互相确认身份、商量好加密方式、并生成一把只有他俩知道的密钥的过程。
我刚开始接触这玩意儿的时候,也觉得很绕。但后来我养成了一个习惯:把 TLS 握手想象成两个特工在接头。你想想看,接头总得先打个招呼、亮个证件、再对个暗号吧?TLS 握手就是干这个的。
标准的 TLS 1.2 握手,通常被称为“四次握手”。注意,这里的“四次”指的是四个主要的消息交换阶段,而不是网络层的四次交互。实际上,它通常需要 2 个 RTT(网络往返时间)。
核心要点:TLS 握手的最终目标只有一个——协商出一份对称加密密钥(Pre-Master Secret),并用它派生出会话密钥。所有复杂的证书交换、随机数生成,都是为了安全地完成这件事。
四次握手概览
我把这四次握手拆解成下面这张图,你先有个整体印象。我建议你把这个流程刻在脑子里,后面所有的抓包分析都是围绕它展开的。
这张图里,我特意把第 4 次握手拆成了两步(客户端 Finished 和服务端 Finished),因为在实际抓包中,你会看到这两个包是分开的。但很多教材把它们合并成一次,你心里有数就行。
第一步:ClientHello —— 客户端亮出底牌
握手的第一步,由客户端发起。客户端会发送一个 ClientHello 消息,相当于在说:“嘿,我想跟你建立安全连接,这是我的能力清单,你看看。”
这个包里都带了些什么?我列个表给你看,这些都是抓包时你需要重点关注的关键字段。
| 字段 | 说明 | 抓包时怎么看 |
|---|---|---|
| Version | 客户端支持的最高 TLS 版本(如 TLS 1.2) | 看 Handshake Type: ClientHello 下的 Version 字段 |
| Random | 32 字节的随机数(4 字节时间戳 + 28 字节随机) | 用于后续密钥派生,每个连接都不同 |
| Session ID | 会话复用标识(可为空) | 非首次连接时,这里会带上上次的 Session ID |
| Cipher Suites | 客户端支持的加密套件列表(按优先级排序) | 重点关注第一个(优先级最高) |
| Extension | 扩展字段(如 SNI、ALPN、Supported Groups) | SNI 里能看到你访问的域名 |
避坑指南:我曾经遇到过一个诡异的问题,客户端连不上某个 HTTPS 网站。抓包一看,ClientHello 里带的 Cipher Suites 列表里全是些过时的套件,比如 TLS_RSA_WITH_AES_128_CBC_SHA。而服务器只支持 ECDHE 系列的套件。结果就是双方谈不拢,握手失败。所以,客户端支持的套件列表一定要与时俱进。
这里有个细节我想强调一下:Random 字段。这个 32 字节的随机数非常重要,它会被用在后续的密钥派生中。每次握手的随机数都是全新的,这就保证了即使你两次访问同一个网站,最终生成的会话密钥也是不同的。这叫“前向安全性”的基础之一。
第二步:ServerHello + 证书交换 —— 服务器回应并亮明身份
服务器收到 ClientHello 后,会做两件事:选套件和发证书。
首先,服务器会从客户端提供的 Cipher Suites 列表里,挑一个它自己也支持的、并且安全性最高的套件。然后,它把自己的选择通过 ServerHello 消息发回去。
紧接着,服务器会发送 Certificate 消息。这个包里装的就是服务器的数字证书。客户端拿到证书后,会做一系列验证:
- 证书链验证:从服务器证书一直追溯到根证书,看是否由受信任的 CA 签发。
- 有效期检查:证书是否在有效期内。
- 域名匹配:证书里的 Common Name (CN) 或 Subject Alternative Name (SAN) 是否跟你访问的域名一致。
- 吊销状态检查:通过 CRL 或 OCSP 检查证书是否被吊销。
嗯,这里要注意,如果服务器要求客户端也出示证书(双向认证),它还会发一个 CertificateRequest 消息。不过这种情况在企业内网或金融系统中比较常见,公网上的 HTTPS 很少用。
警告:千万不要忽略证书验证!我见过有人为了省事,在代码里把证书验证给关了(比如设置 verify=False)。这在开发环境可能没问题,但一旦上了生产环境,就等于把大门敞开,中间人攻击可以轻松得手。我曾经在渗透测试中,就利用这种配置抓到了某内部系统的明文密码。
第三步:密钥交换 —— 核心机密协商
这是整个握手过程中最核心、也最烧脑的一步。客户端和服务器需要协商出一个Pre-Master Secret,然后各自用这个秘密加上之前交换的两个 Random,派生出最终的会话密钥。
密钥交换的方式主要有两种,我对比一下:
| 方式 | 原理 | 安全性 | 现状 |
|---|---|---|---|
| RSA 密钥交换 | 客户端生成 Pre-Master Secret,用服务器公钥加密后发送 | 低(私钥泄露可解密所有历史流量) | 已废弃,不推荐使用 |
| (EC)DHE 密钥交换 | 双方交换 DH 参数,各自计算 Pre-Master Secret | 高(具备前向安全性) | 主流推荐,TLS 1.3 强制使用 |
我个人强烈建议你使用 ECDHE(椭圆曲线 Diffie-Hellman 临时密钥交换)。为什么?因为它具备前向安全性。说白了,就算攻击者拿到了服务器的私钥,他也只能解密当前连接,无法解密之前记录下来的历史流量。RSA 密钥交换就不行,私钥一丢,所有历史数据全完蛋。
在 ECDHE 流程中,服务器会在 ServerKeyExchange 消息里带上自己的 DH 参数(包括椭圆曲线、基点、公钥等),并用私钥对这些参数签名。客户端收到后,验证签名,然后生成自己的 DH 公钥,通过 ClientKeyExchange 发回去。最后,双方各自用对方的公钥和自己的私钥,计算出相同的 Pre-Master Secret。
关键点:在 ECDHE 中,Pre-Master Secret 是双方各自计算出来的,从未在网络上传输。即使攻击者截获了所有 DH 公钥,也无法反推出 Pre-Master Secret(这依赖于椭圆曲线离散对数问题的计算难度)。
第四步:Finished 验证 —— 确认握手成功
最后一步,双方各自发送 ChangeCipherSpec 和 Finished 消息。
- ChangeCipherSpec:这是一个通知,告诉对方:“接下来我要开始用协商好的密钥加密通信了。”
- Finished:这是一个加密的消息,里面包含了之前所有握手消息的哈希值。双方都会验证这个哈希值,确保握手过程中没有任何消息被篡改。
如果双方的 Finished 消息都能被对方正确解密和验证,那么握手就成功了。之后,所有的应用数据都会使用协商好的对称密钥进行加密传输。
我记得有一次排查一个性能问题,发现握手耗时特别长。抓包一看,问题出在 Finished 消息上——客户端发完 ChangeCipherSpec 后,服务器迟迟没有响应。后来发现是服务器端的证书吊销检查(OCSP)超时了。所以,Finished 阶段的延迟,往往不是加密本身的问题,而是证书验证或网络延迟导致的。
好了,TLS 四次握手的核心流程就这些。你把这个骨架搭好,后面我们抓包分析的时候,就能一眼看出每个包属于哪个阶段,问题出在哪里。