3. TLS协议体系结构:记录协议与握手协议

TLS协议栈,说白了就是一套分层设计的“安全通信脚手架”。我刚开始接触的时候也觉得很绕,什么记录层、握手层、警报协议……其实理清脉络就很简单。你想想看,如果把TLS比作一栋房子,那记录协议就是地基,负责搬运和封装数据;握手协议是施工队,负责建立信任关系;而警报协议就是火灾报警器——平时看不见,有事才出声。

3.1 整体架构:协议栈的层次关系

咱们先从上往下看这张层次图。应用层的HTTP、FTP这些数据,先被TLS“套”进来,然后一层层处理,最后交给TCP发送。我个人习惯把这四层协议画成一张垂直的栈图,贴在工位上。

一句话记住:应用层跑业务,握手层管连接,记录层管加密和传输,TCP管可靠。
TLS协议栈层次结构图 TLS协议栈层次结构 应用层 HTTP / FTP / SMTP 等应用数据 TLS握手子层 握手协议 | 警报协议 | 变更密码规范 TLS记录协议 分片 → 压缩 → 加密 → MAC → 发送 TCP传输层 可靠连接、字节流、序列号 关键特性 • 应用层:不感知TLS存在 • 握手子层:非对称密钥交换 • 记录层:对称加密 & HMAC • TCP层:保证不丢不重

从上图可以看到:应用数据先被握手子层处理(比如协商密钥、交换证书),然后交给记录协议进行分片、加密、添加MAC,最后塞进TCP的字节流里。嗯,这里要注意:记录协议是真正的“搬运工”,其他所有子层的数据最终都得通过它来发送。

3.2 记录协议:TLS的“运输工”

记录协议负责把上层数据拆成固定大小的“记录”,然后挨个处理。每个记录都包含内容类型、版本、长度、负载和MAC。我在项目中遇到过某款老旧设备,硬是把记录长度设为16KB,结果客户端频繁超时——后来才发现是分片策略太粗糙。

3.4.1 记录格式

struct {
    ContentType type;       // 1字节:握手(22)、警报(21)、应用数据(23)
    ProtocolVersion version; // 2字节:03 03 代表TLS 1.2
    uint16 length;          // 2字节:负载长度,最大2^14字节
    opaque fragment[length]; // 变长:实际加密数据
} TLSRecord;

每个记录头只有5个字节。你想想看,这5个字节里的内容类型,决定了接收方怎么解析后续数据。比如type=22,说明这是握手消息;type=23,说明这是加密后的应用数据。

记忆技巧:内容类型+版本+长度+数据。每次看到抓包里的“Handshake(22)”就明白了——哦,这是握手阶段。

3.4.2 记录协议处理流程

记录协议的处理分三个步骤:接收上层数据后,先分片,然后压缩(可选,TLS 1.3已废除),最后加密+MAC。我用个表格把这些动作讲清楚。

步骤 输入 处理方式 输出
1. 分片 应用层数据(如HTTP请求) 切分为≤2^14字节的片 多个TLSPlaintext
2. 压缩(可选) TLSPlaintext Deflate/Null压缩(TLS 1.3强制Null) TLSCompressed
3. 加密+MAC TLSCompressed + 序列号 AES-GCM / ChaCha20-Poly1305等 TLSCiphertext
曾经有个坑:记录协议维护了一个隐式的序列号(从0开始递增),用于计算MAC。我曾经见过开发的伙计在重传时忘了更新这个序列号,结果服务端MAC校验一直失败。记住:序列号不能重置,必须连续递增。

3.3 握手协议:TLS的“施工队”

握手协议是整个TLS的精华。它负责协商密码套件、交换密钥、验证身份。握手完成前,所有数据都是明文或在“即将加密”的状态。我常说:握手阶段就像是双方在互相打量——“你是谁?”“你用什么算法?”“你的证书是真的吗?”

3.3.1 握手流程概览

一个标准的TLS 1.2完整握手包含以下消息(我简化一下,去掉可选扩展):

  1. ClientHello:客户端发送支持的版本、密码套件列表、随机数
  2. ServerHello:服务端选择版本、一个密码套件,发回随机数
  3. Certificate(服务端):发送自己的X.509证书链
  4. ServerKeyExchange(如果需要):发送DH参数或ECDHE公钥
  5. ServerHelloDone:服务端说“我发完了”
  6. ClientKeyExchange:客户端发送自己的公钥或pre-master secret
  7. ChangeCipherSpec(双方):通知记录层切换加密策略
  8. Finished(双方):发送握手消息的MAC,验证完整性

嗯,这里要注意:ChangeCipherSpec其实不属于握手协议,它属于变更密码规范协议——但大家习惯把它看作握手的一部分。

避坑指南:我曾经调试过一个奇怪的问题:握手一直失败,最后发现是服务端发的Certificate消息太大(证书链有4级,总共8KB),超过了记录层默认的16KB限制。注意:如果证书链太大,服务端应该发多个Certificate消息,或者用压缩。

3.3.2 密码套件在握手中的作用

握手过程中,密码套件像一个“菜单”。客户端在ClientHello里列出它支持的菜(比如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256),服务端从里面选一个最合胃口的。选好之后,双方就知道用什么密钥交换、用什么对称加密、用什么哈希。

我把密码套件拆开讲,它包含四个部分:

  • 密钥交换算法:如ECDHE、DHE、RSA
  • 身份验证算法:如RSA、ECDSA、DSS
  • 对称加密算法+模式+密钥长度:如AES_128_GCM、ChaCha20_Poly1305
  • PRF和MAC算法:如SHA256、SHA384

举个例子,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256的含义是:

组件 值 说明
密钥交换 ECDHE 椭圆曲线Diffie-Hellman临时密钥,提供前向安全性
身份验证 RSA 用RSA签名验证服务端证书
对称加密 AES_128_GCM 128位密钥的AES-GCM模式,支持认证加密
MAC/PRF SHA256 用于密钥派生和消息认证

3.4 警报协议:TLS的“故障灯”

警报协议是个低调的角色。它只有两个消息:warning和fatal。fatal警报一旦收到,连接必须立即关闭。我见过不少开发者在代码里把fatal当warning处理——结果数据错乱、会话重建,白白浪费时间。

常见的警报类型:

  • close_notify:正常关闭连接通知
  • unexpected_message:收到意料之外的消息
  • bad_record_mac:MAC校验失败(加密数据被篡改)
  • handshake_failure:握手协商失败
  • decrypt_error:解密失败(可能是密钥不匹配)
记住一条铁律:fatal警报必须立即终止会话,不能重试。我曾经见过测试环境里反复重试bad_record_mac——结果导致服务端panic。别这么干。

3.5 小结与核心要点

回看这张协议栈图,我想强调三点:

  1. 记录协议是基石,它负责分片、加密、传输——所有上层消息最终都得经过它。
  2. 握手协议是核心,它决定了后续通信使用的密码套件和密钥。
  3. 警报协议是安全边界,它确保一旦发现异常,双方能优雅或强制地中止连接。

下次你抓包看到一堆43字节的握手记录,或者5字节的记录头,就能轻松判断:哦,这是ClientHello,那是ServerHelloDone。理解这些“小零件”,才能真正驾驭TLS。