1. TLS 握手协议基础:协议栈架构、记录协议与握手协议的分工、四次握手流程详解
聊到 TLS 优化,很多人一上来就盯着加密套件、会话复用这些细节。但我个人习惯,先把这个协议栈的骨架搭清楚。你想想看,如果连记录协议和握手协议谁负责干什么都分不清,后面调优的时候很容易抓瞎。
这一章,我们就从最底层开始,把 TLS 协议栈的架构、两个核心子协议的分工,以及经典的四次握手流程彻底捋一遍。嗯,这些都是后面 29 章的基础,咱们得打扎实了。
1.1 TLS 协议栈架构:它到底长什么样?
TLS 协议并不是一个单一的协议,它是一组协议的集合。从网络协议栈的角度看,它位于传输层(TCP)和应用层(HTTP、FTP 等)之间。
我习惯把它分成上下两层:
- 底层:TLS 记录协议(Record Protocol) —— 负责数据的搬运工。它把上层的数据分片、压缩(可选)、加密、添加消息认证码(MAC),然后交给 TCP 发送。
- 上层:TLS 握手协议(Handshake Protocol) —— 负责连接的建立。它协商加密参数、交换证书、生成会话密钥。此外,还有警告协议(Alert Protocol)和变更密码规范协议(Change Cipher Spec Protocol)这两个小兄弟辅助。
说白了,握手协议是“谈条件”的,记录协议是“干活”的。谈好了条件,记录协议才开始加密传输数据。
核心理解: 记录协议是 TLS 的“传输容器”,所有 TLS 数据(包括握手消息本身)最终都要封装在记录协议的数据帧里。你抓包看到的 TLS 记录,就是这玩意儿。
下面这张图,是我自己画的一个协议栈分层示意,能帮你快速建立整体印象:
1.2 记录协议与握手协议的分工
这两个协议的分工,我经常用一个比喻来解释:握手协议是“外交官”,记录协议是“邮递员”。
外交官负责谈判,确定用什么密码、交换钥匙。谈妥之后,邮递员才开始工作,把真正的应用数据打包、加密、贴上标签(MAC),然后送出去。
具体来说:
- 握手协议:负责身份认证和密钥协商。它交换的消息包括 ClientHello、ServerHello、证书、密钥交换参数等。这些消息本身也是通过记录协议传输的,但此时记录协议还没有启用加密,处于“明文”状态。
- 记录协议:负责数据的安全传输。它接收上层的数据,进行分片(最大 16KB)、压缩(TLS 1.3 已废弃)、添加 MAC、加密,然后加上记录头(ContentType、版本、长度)交给 TCP。
一个小技巧: 抓包的时候,看记录头的 ContentType 字段就能知道这条记录是干什么的。0x16 是握手协议,0x17 是应用数据,0x15 是警告协议。这个在排查问题时非常有用。
我曾经在排查一个 HTTPS 慢的问题时,发现客户端和服务端在握手阶段来回发送了很多次“Hello Retry Request”。当时就是通过抓包看 ContentType 和握手消息类型,才定位到是密码套件不匹配导致的。嗯,这个后面讲 TLS 1.3 的时候会细说。
1.3 四次握手流程详解
经典的 TLS 1.2 完整握手,一共需要 2 个 RTT(网络往返)。很多人把它叫做“四次握手”,其实是指客户端和服务端之间交互了 4 次消息(不算 TCP 的三次握手)。
咱们一步步拆开看:
第一次握手:ClientHello
客户端发起连接。它告诉服务端:
- 支持的 TLS 版本(比如 TLS 1.2)
- 支持的密码套件列表(比如 ECDHE-RSA-AES128-GCM-SHA256)
- 一个随机数(Client Random)
- 可选的 Session ID 或 SNI(服务器名称指示)
第二次握手:ServerHello + 证书 + ServerHelloDone
服务端回应。它做三件事:
- ServerHello:选定 TLS 版本和密码套件,发送自己的随机数(Server Random)。
- Certificate:发送自己的数字证书(包含公钥)。
- ServerHelloDone:告诉客户端“我说完了,该你了”。
注意: 如果服务端要求客户端也提供证书(双向认证),这里还会发送一个 CertificateRequest 消息。不过实际项目中,双向认证用得不多,我只有在金融行业的内网接口中见过几次。
第三次握手:ClientKeyExchange + ChangeCipherSpec + Finished
客户端收到证书后,验证证书合法性(CA 链、域名、有效期等)。验证通过后:
- ClientKeyExchange:根据协商的密钥交换算法(如 ECDHE),发送客户端密钥交换参数(比如公钥)。
- ChangeCipherSpec:告诉服务端“我要切换加密模式了,后面的消息都是加密的”。
- Finished:发送一个加密的验证消息,包含之前所有握手消息的摘要。服务端用这个来验证握手没有被篡改。
第四次握手:ChangeCipherSpec + Finished
服务端收到客户端的 Finished 并验证通过后:
- ChangeCipherSpec:同样切换加密模式。
- Finished:发送加密的 Finished 消息,让客户端验证。
至此,握手完成。双方开始用协商好的会话密钥加密应用数据。
我把这个流程整理成了一张表格,方便你对照:
| 步骤 | 方向 | 消息类型 | 关键内容 | 加密状态 |
|---|---|---|---|---|
| 1 | C → S | ClientHello | 版本、密码套件列表、Client Random | 明文 |
| 2 | S → C | ServerHello + Certificate + ServerHelloDone | 选定套件、Server Random、证书 | 明文 |
| 3 | C → S | ClientKeyExchange + CCS + Finished | 密钥交换参数、切换加密、握手摘要 | 明文 → 加密 |
| 4 | S → C | CCS + Finished | 切换加密、握手摘要 | 明文 → 加密 |
看到这里,你可能会问:为什么 Finished 消息要放在 CCS 之后?
原因很简单:Finished 消息是第一条被加密的消息。它用来验证双方协商出来的密钥是否一致。如果密钥不一致,解密会失败,握手也就失败了。这是一种安全校验机制。
避坑指南: 我曾经遇到过一个线上问题,客户端和服务端握手一直失败,抓包发现 Finished 消息解密失败。最后定位到是服务端配置的密码套件中,密钥交换算法和证书类型不匹配(比如用了 ECDHE 但证书是 RSA 的,没有 ECDSA 扩展)。这种问题在升级 OpenSSL 版本后尤其容易出现。
好了,这一章的内容就到这里。TLS 协议栈的骨架、记录协议和握手协议的分工、四次握手的详细流程,咱们都过了一遍。这些是后面所有优化手段的基础,理解透了,后面讲会话复用、0-RTT、TLS 1.3 优化的时候,你才能知道它们到底优化了哪里。