2. TLS 1.2 完整握手流程:从 ClientHello 到 Finished

TLS 1.2 的完整握手,说白了就是客户端和服务器之间的一场「身份确认 + 密钥协商」的对话。我刚开始调优的时候,总觉得这过程太啰嗦——来回要 2 个 RTT 才能开始传数据。但后来在项目中踩过几次坑,才明白每一步都有它存在的道理。

咱们一步步拆开看。整个流程分五个阶段:

  • ClientHello —— 客户端打招呼,亮出自己支持的密码套件
  • ServerHello + 证书交换 —— 服务器回应,并亮出身份证
  • 密钥协商 —— 双方悄悄约定一个只有彼此知道的密钥
  • ChangeCipherSpec + Finished —— 确认密钥生效,握手完成

核心要点:整个握手过程,客户端和服务器各发 2 次消息,共消耗 2 个 RTT。这 2 个 RTT 就是延迟优化的主战场。

TLS 1.2 完整握手流程图 TLS 1.2 完整握手流程 客户端 服务器 ① ClientHello 密码套件 + 随机数 + SNI ② ServerHello 选定套件 + 随机数 ③ 证书 + ServerKeyExchange 证书链 + 公钥 ④ ClientKeyExchange Pre-Master Secret (RSA 加密) ⑤ Finished ⑤ Finished 验证握手完整性 共 2 个 RTT:ClientHello → ServerHello (1 RTT) + 密钥协商 → Finished (1 RTT) 优化方向:减少证书大小、启用会话复用、TLS 1.3 0-RTT

2.1 ClientHello —— 客户端亮出底牌

握手的第一步,客户端先发一个 ClientHello 消息。这里面包含了什么?我列一下关键字段:

字段 说明 对延迟的影响
Version 客户端支持的最高 TLS 版本 无直接影响
Random 32 字节随机数,用于后续密钥派生 无直接影响
Cipher Suites 客户端支持的密码套件列表 列表越长,包越大,可能触发分片
Extension: SNI 告诉服务器要访问哪个域名 无直接影响,但缺失会导致证书不匹配
Extension: Supported Groups 支持的椭圆曲线列表 ECDHE 密钥交换时影响协商速度

我的经验:有一次排查一个握手慢的问题,发现 ClientHello 包大小超过了 512 字节,触发了 TCP 分片。客户端和服务器之间刚好有中间设备对分片处理很慢,导致第一个 RTT 就多了 200ms。后来我把不必要的密码套件砍掉,包大小降到了 400 字节以内,问题就解决了。

2.2 ServerHello + 证书交换 —— 服务器回应并亮身份

服务器收到 ClientHello 后,会做两件事:

  1. 选择密码套件:从客户端列表里挑一个它支持的。我建议服务器端把密码套件列表按优先级排好,别让服务器去「猜」。
  2. 发送证书:把证书链发给客户端。这里有个坑——证书链太长会显著增加握手延迟。

证书交换这一步,我见过最夸张的情况:一个客户的证书链有 5 级,加起来 12KB。在移动网络下,光传证书就花了 300ms。你想想看,用户打开一个网页,光握手就等半秒,体验能好吗?

避坑指南:我曾经遇到过一个案例,服务器发了完整的证书链,但中间证书的 OCSP 响应没带上。客户端不得不额外发一个 OCSP 请求去验证证书状态,又多了一个 RTT。解决方案是启用 OCSP Stapling,让服务器把 OCSP 响应直接塞在 Certificate 消息里。

2.3 密钥协商 —— 悄悄约定一个共享秘密

这一步是握手最核心的部分。客户端用服务器的公钥加密一个 Pre-Master Secret,发给服务器。双方再用这个 Pre-Master Secret 加上之前交换的随机数,派生出 Master Secret,最后生成会话密钥。

这里有两种主流方式:

方式 原理 安全性 延迟影响
RSA 密钥交换 客户端生成 Pre-Master Secret,用服务器公钥加密后发送 不支持前向安全性 计算量小,延迟低
ECDHE 密钥交换 双方交换椭圆曲线参数,各自计算共享密钥 支持前向安全性 多一次 ServerKeyExchange 消息,计算量稍大

我的建议:虽然 ECDHE 比 RSA 多一次消息交换,但前向安全性太重要了。我参与过的项目,只要涉及用户敏感数据,一律强制使用 ECDHE。多花几十毫秒,换来的是即使服务器私钥泄露,历史会话也不会被破解。

2.4 ChangeCipherSpec + Finished —— 确认密钥生效

密钥协商完成后,双方各自发送 ChangeCipherSpec 消息,告诉对方「接下来我要用新密钥加密了」。然后发送 Finished 消息,里面是对整个握手过程的 HMAC 校验值。

为什么要发 Finished?说白了就是防止握手过程中有人篡改消息。如果客户端算出来的 HMAC 和服务器发来的不一致,握手就会失败。

一个小细节:ChangeCipherSpec 本身不是 TLS 1.2 握手协议的一部分,它属于记录层协议。但它在握手过程中出现,所以很多人把它算作握手的一步。嗯,这里要注意,别搞混了。

2.5 完整握手的延迟构成

咱们算一笔账。假设客户端和服务器之间的 RTT 是 50ms:

  • 第 1 个 RTT:ClientHello → ServerHello + 证书 (50ms)
  • 第 2 个 RTT:ClientKeyExchange → Finished (50ms)
  • 额外开销:证书传输时间(假设 5KB 证书,10Mbps 带宽,约 4ms)+ 密钥计算时间(ECDHE 约 1-2ms)

总耗时大约 105-110ms。这还没算 TCP 三次握手的 1 个 RTT。所以一个全新的 HTTPS 连接,从 TCP 建连到 TLS 握手完成,至少需要 3 个 RTT 加上证书传输时间。

注意:如果证书链很大,或者网络带宽很低,证书传输时间可能成为瓶颈。我见过一个极端案例:某海外站点证书链 15KB,用户通过 2G 网络访问,光证书传输就花了 2 秒。优化方案是精简证书链,去掉不必要的中间证书。

2.6 实战中的常见问题

我在项目中总结了几条高频踩坑点:

  1. 密码套件列表太长:ClientHello 包过大,触发 TCP 分片或 MTU 问题。
  2. 证书链不完整:客户端需要额外下载中间证书,增加 RTT。
  3. OCSP 响应缺失:客户端主动验证证书状态,多一次 HTTP 请求。
  4. 密钥交换算法选择不当:RSA 虽然快但不安全,ECDHE 安全但稍慢。
  5. Finished 消息校验失败:通常是中间设备篡改或缓存了握手消息。

总结一下:TLS 1.2 完整握手虽然可靠,但 2 个 RTT 的开销在延迟敏感场景下不可忽视。优化的核心思路就是:减少 RTT 次数、压缩证书大小、启用会话复用。这些内容咱们后面几章会详细展开。