第2章:TLS握手基本流程

TLS 握手,说白了就是客户端和服务器在正式传输数据前互相认识的仪式。这个仪式的核心目的有三个:确认双方身份(服务器必须验明正身)、协商加密算法、生成一个只有你我知道的对称密钥。

我刚开始调优 HTTPS 性能时,总觉得握手不就是多打几个包吗?后来用 Wireshark 一抓包才发现,光握手的网络往返就能占掉页面加载时间的 30% 以上。你想想看,一个普通的 TLS 1.2 握手至少需要两次网络往返,如果客户端和服务器之间延迟有 100ms,光握手就浪费了 200ms。这部分时间,完全可以优化。

接下来,我们一步步拆解这个过程。

2.1 ClientHello —— 客户端打招呼

握手的第一步,客户端先发一个 ClientHello 消息给服务器。它就像一张名片,上面写着自己的身份和能力。消息里主要包含下面几项内容:

  • 支持的 TLS 版本:比如 TLS 1.2、TLS 1.3。客户端会把自己能支持的最高版本放上去。
  • 客户端随机数:一个 32 字节的随机值,后续用来生成主密钥。
  • 加密套件列表:客户端支持的所有加密算法组合。比如 TLS_AES_128_GCM_SHA256、TLS_CHACHA20_POLY1305_SHA256 等。
  • 扩展字段:比如 SNI(域名指示)、ALPN(应用层协议协商)。我个人认为 SNI 这个扩展特别重要,没有它一个 IP 只能部署一个 HTTPS 站点。

嗯,这里有个细节。ClientHello 包的大小如果超过 512 字节,可能会被中间设备分片。我在项目中曾遇到过因为客户端支持的加密套件太多,导致 ClientHello 包过大的情况。最终排查发现是某个老旧设备丢掉了分片后的第二个 TCP 段,握手直接失败。

避坑指南:我曾经优化过一个电商 App 的 TLS 握手。客户端加密套件列表写了 30 多个,全部删掉冗余项后,ClientHello 包从 600 字节降到了 380 字节。虽然只少了 220 字节,但在弱网环境下,这个小改动确实降低了 TCP 分片概率。

2.2 ServerHello 与证书交换

服务器收到 ClientHello 后,会做两件事:一是从客户端支持的列表中选一个加密套件,二是准备自己的身份证明——证书。

ServerHello 消息包含:

  • 选定的 TLS 版本
  • 服务器生成的随机数(同样 32 字节)
  • 选定的加密套件

紧接着,服务器会发送 Certificate 消息。这个证书本身是一串 X.509 格式的数据,里面包含服务器的公钥、颁发者信息、有效期。浏览器拿到这份证书后,会沿着证书链一层层往上验证,直到根证书。

为什么需要证书呢?说白了就是为了证明“你就是你自己”。否则中间人完全可以伪造一个公钥来冒充你的服务器。

关键点:证书的大小直接影响握手延迟。我见过很多运维人员直接把证书链里所有中间证书都发出去,结果 Cert 消息超过 4KB。一个 4KB 的证书包在网络状况差的时候,可能要多等一个 RTT 才能传完。建议始终检查证书链,只发送必要的证书,通常只需要服务器证书 + 一个中间 CA 证书即可。

服务器也可能发送 ServerKeyExchange,这个在 DHE 或 ECDHE 密钥交换算法中会出现,用来携带 DH 参数。如果是 RSA 密钥交换,这条消息就可以省了。

2.3 密钥协商过程

客户端收到服务器发来的证书和 DH 参数后,必须先验证证书的合法性。验证包括:

  1. 证书是否由受信任的 CA 签发
  2. 证书是否在有效期内
  3. 证书域名是否与当前访问的域名匹配
  4. 证书是否被吊销(CRL 或 OCSP 检查)

验证通过后,客户端会发送 ClientKeyExchange 消息。这里面携带了客户端生成的 DH 公钥(或者 RSA 加密的预主密钥)。

为什么需要双方分别生成随机数?你想想看,TLS 的 预主密钥 是由客户端产生的(在 RSA 模式下由客户端生成随机 48 字节),但主密钥的生成需要 客户端随机数 + 服务器随机数 + 预主密钥 三者经过 PRF 函数推导。这样设计的好处是,即使一方的随机数不够随机,另一方也能保证最终密钥的熵足够。

最后,客户端会发送 ChangeCipherSpec 和 Finished 消息,表示之后的通信都使用刚才协商好的密钥加密。服务器验证 Finished 消息正确后,也会发送自己的 ChangeCipherSpec 和 Finished。到此,TLS 握手完成。

注意:Finished 消息是对整个握手过程的 HMAC 校验。如果中间有人篡改了任何一个握手消息,两边的计算值就会对不上,握手立刻失败。这是保护握手完整性的关键机制。

2.4 传统 TLS 1.2 与 TLS 1.3 握手对比

既然聊到这里,我顺便把 TLS 1.2 和 1.3 的流程做个对比。TLS 1.3 最大的改进就是一次往返(1-RTT)即可完成握手。它把 ServerHello 以后的证书和密钥交换合并了,减少了不必要的等待。

特性 TLS 1.2 TLS 1.3
握手往返次数 2-RTT(全握手) 1-RTT(首次连接)
加密套件协商 客户端提供全部套件,服务器选择 客户端只提供有限套件,服务器选择
密钥交换算法 RSA / DHE / ECDHE 仅 ECDHE(静态 RSA 禁用)
证书传输 在 ServerHello 之后独立发送 在 ServerHello 的扩展中一并发送
0-RTT 支持 不支持 支持(基于 PSK)

我个人强烈建议新项目直接用 TLS 1.3。不仅握手快,而且去掉了 RSA、CBC 等不安全算法,从协议层面就规避了很多已知攻击。当然,如果你的业务还要兼容一些十年前的老设备,那可能还得保留 TLS 1.2。

接下来,我们用一张流程图把刚才讲的所有步骤串起来,方便记忆。

TLS 1.2 全握手流程 客户端 服务器 ClientHello ServerHello Certificate ClientKeyExchange ChangeCipherSpec Finished Server Finished 🔑 握手完成 开始加密通信 ① ② ③ ④ ⑤ ⑥

这张图展示的是标准的 TLS 1.2 全握手流程。从 ClientHello 出发,经过 ServerHello、证书交换、密钥交换,最后双方用 Finished 消息确认。注意图中第③步是 ClientKeyExchange 先发,然后服务器收到后才发 ChangeCipherSpec 和 Finished。每一步都对应一个网络往返。