TLS 1.2 握手流程:核心四步

TLS 1.2 的握手,说白了就是客户端和服务器互相亮明身份、协商密码套件、交换密钥材料的过程。我调 HSM 性能时,经常要盯着这四步看耗时——哪一步慢了,问题出在哪,心里得有数。

核心阶段一览
  • ClientHello — 客户端打招呼,告诉服务器自己支持哪些版本、套件、扩展
  • ServerHello — 服务器回一声,选定套件并回复随机数
  • 证书交换 — 服务端发证书(可选要求客户端证书),客户端验证证书链
  • 密钥协商 — 基于公钥密码(RSA 或 DH)协商出预主密钥,再派生出会话密钥
TLS 1.2 握手四步流程图 ① ClientHello ② ServerHello ③ 证书交换 ④ 密钥协商 客户端发送:TLS版本、密码套件列表、随机数、Session ID 服务器选择:版本、套件、生成随机数,回应Hello 发送证书链(可选请求客户端证书),客户端验证 计算预主密钥 → 主密钥 → 会话密钥(发送ChangeCipherSpec)

1. ClientHello —— 客户端的第一声招呼

握手刚开始,客户端会发一个 ClientHello 消息。里面带着:

  • 支持的 TLS 版本(比如 1.2,但要兼容 1.1、1.0)
  • 密码套件列表(Cipher Suites,几十个,按优先级排好)
  • 随机数(32 字节,其中 4 字节时间戳,28 字节随机)
  • Session ID(用于会话恢复,可以空)
  • 扩展字段(SNI、ALPN、EC曲线等)

我在项目中遇到过一件挺坑的事:有个客户把密码套件列表排得太长,超过一个 MTU,导致握手包被分片。HSM 那边处理分片会多一次 I/O,性能直接掉了 30%。嗯,后来我建议他们只保留 10 个最常用的套件,顺序也按协商速度排好,问题一下就解决了。

我的小技巧:调试时可以用 Wireshark 抓个 ClientHello 看看扩展长度。如果超过 512 字节,大概率要出分片问题。

2. ServerHello —— 服务器的回应

服务器收到 ClientHello 后,选出它也支持且策略允许的最高版本和最优先的套件,然后发回 ServerHello。核心内容:

  • 选定的 TLS 版本(一定是客户端列表里有的)
  • 选定的密码套件(一个 16 位整数编号)
  • 服务器随机数(也是 32 字节,不含时间戳也行)
  • Session ID(如果要做会话恢复,服务器会分配一个)

注意:服务器随机数加上客户端随机数,后面要用于密钥派生。你想想看,如果服务器随机数生成用了弱随机数生成器,整个 TLS 会话的密钥都不安全。我曾经帮一个客户排查 HSM 调用慢的问题,发现他们服务器端用了一个老旧的伪随机函数,每次生成 32 字节随机数居然要 5 毫秒。换成硬件 TRNG 后,降到 0.1 毫秒。

3. 证书交换 —— 身份的凭证

接着就是服务器把证书链发过来。如果是双向认证,服务器还会发 CertificateRequest,要求客户端也发证书。这个阶段的关键点:

证书交换关键字段
消息类型作用常见坑
Certificate服务端证书链(从叶子到根)链太长导致握手包过大
ServerKeyExchangeDH 参数(仅当使用 DHE/ECDHE 时发送)参数长度不符合 RFC
CertificateRequest要求客户端证书(可选)客户端没有时握手失败

我习惯把证书链控制在 3 层以内(叶子+中间+根),超过 3 层不仅增加字节数,还增加 HSM 验签次数。有一次遇到一个甲方,把 6 级 CA 链全塞进去,握手光证书传输就花了 80 毫秒。后来只保留到中间 CA,根证书由信任库本地验证,时间砍到 20 毫秒。

避坑提醒:我曾经见过一个案例,服务器发了 ServerKeyExchange 但里面的 DH 参数 p 是固定的 1024 位素数,客户端一检查发现不符合 RFC 7919 的要求,直接 abort。现在推荐至少 2048 位 DH 参数,或者改用 ECDHE。

4. 密钥协商 —— 核心计算

这一步是算法密集型操作,也是 HSM 性能优化的主战场。根据选定的密码套件,有两种路径:

  • RSA 密钥协商:客户端生成 46 字节预主密钥,用服务器公钥加密后发给服务器。服务器用私钥解密。好处是简单,但前向安全性不足。
  • Diffie-Hellman(DHE/ECDHE):双方交换 DH 参数,各自计算共享秘密。提供前向安全性,但计算量更大。

无论哪种路径,最终都会用 PRF(伪随机函数) 把预主密钥、客户端随机数、服务器随机数一起揉成 主密钥,再从主密钥派生出 6 条会话密钥(客户端写密钥、服务端写密钥、IV 等)。

我在 HSM 优化项目里最常干的事就是让客户把 RSA 密钥协商换成 ECDHE。为什么?因为 HSM 做 RSA 解密(1024 或 2048 位)时,是纯模幂运算,单次大概 1-2 毫秒;而 ECDHE 的 ECC 点乘在 HSM 上可以做到 0.3 毫秒以下。如果每秒 10000 次握手,差距就是 10 秒 vs 3 秒,量级上非常可观。

我的经验值:如果 HSM 支持 ECC 的硬件加速,尽量选 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。证书验证仍用 RSA 公钥,密钥协商用 ECDHE,前向安全+性能兼备。

别忘了最后一步:双方发送 ChangeCipherSpec 通知对方后面的数据都将加密。然后发 Finished 消息带 HMAC 验证完整性。这个验证也要走 HSM 做一次对称加密,但相比密钥协商那一步,开销小很多。

其实整个 TLS 1.2 握手,我最想强调的就是:密钥协商是 HSM 的瓶颈所在。我在一个峰值 10 万 QPS 的网关项目里,发现 HSM 的 RSA 解密队列满到溢出,后来改成 ECDHE + 使用 HSM 内置的 ECC 协处理器,才把性能拉回来。你设计架构时,一定要想清楚你的 HSM 到底擅长什么。