4. TLS 1.2 协议详解:记录、握手、警报与密码套件
好,咱们今天来啃 TLS 1.2 这块硬骨头。说实话,TLS 协议栈看起来层数多,但核心就四个部分:记录协议、握手协议、警报协议,以及贯穿始终的密码套件。我当年刚接触时,总觉得握手协议最复杂,后来才发现,不理解记录协议,握手协议也白搭。
咱们一层层剥开看。
4.1 TLS 记录协议:最底层的搬运工
记录协议是 TLS 的基石。所有上层数据——握手消息、警报、应用数据——最终都要封装成记录,再交给 TCP 发送。
每条记录长这样:
struct {
ContentType type; // 内容类型(20=ChangeCipherSpec, 21=Alert, 22=Handshake, 23=Application Data)
ProtocolVersion version; // 版本(TLS 1.2 为 0x0303)
uint16 length; // 负载长度
opaque fragment[TLSPlaintext.length]; // 实际数据
} TLSPlaintext;
注意,记录协议本身不加密。加密是在记录层之上,由 ChangeCipherSpec 消息触发后,后续记录才用协商好的密钥加密。我见过有人调试时抓包看到明文握手,以为没加密——其实那是正常的,握手阶段本身就不加密。
4.2 TLS 握手协议:建立信任的谈判桌
握手协议是 TLS 最复杂的部分。说白了,就是客户端和服务器互相确认身份、协商密钥的过程。TLS 1.2 的握手流程,我拆成四个阶段来讲。
4.2.1 第一阶段:Hello 与能力协商
客户端先发 ClientHello,带上自己支持的 TLS 版本、密码套件列表、随机数等。服务器回复 ServerHello,选定版本和套件,也发一个随机数。
这里有个坑:客户端可能同时支持 TLS 1.2 和 1.3,但服务器只支持 1.2。如果客户端发的版本号是 1.3,服务器会拒绝吗?不会。TLS 的版本协商机制允许客户端发一个较高的版本,服务器选自己能支持的最高版本。我遇到过某款嵌入式设备,固件里写死了 ClientHello 版本为 1.2,结果升级后的服务器只认 1.3 的扩展字段——握手失败。嗯,兼容性测试一定要做。
4.2.2 第二阶段:服务器证书与密钥交换
服务器发 Certificate 消息,把自己的证书链发给客户端。接着发 ServerKeyExchange(如果需要的话),最后发 ServerHelloDone 表示“我说完了”。
什么时候需要 ServerKeyExchange?当使用 DHE 或 ECDHE 密钥交换时,服务器需要把 DH 参数发给客户端。如果是 RSA 密钥交换,这一步可以省略。我建议你优先用 ECDHE,前向安全性更好。
4.2.3 第三阶段:客户端证书与密钥交换
客户端收到服务器消息后,先验证服务器证书。验证通过后,发 ClientKeyExchange 消息。如果是 RSA 套件,这里传的是用服务器公钥加密的预主密钥;如果是 DHE/ECDHE,这里传的是客户端的 DH 参数。
然后客户端发 ChangeCipherSpec,告诉服务器“我要开始加密了”。最后发 Finished 消息,里面是对整个握手过程的 MAC 校验。
4.2.4 第四阶段:服务器完成
服务器也发 ChangeCipherSpec 和 Finished。至此,握手完成,双方开始用协商好的密钥加密应用数据。
整个握手流程,我用一张图来总结:
4.3 TLS 警报协议:优雅的错误处理
警报协议很简单,就两种级别:Warning 和 Fatal。Fatal 级别的警报会立即终止连接。
常见的警报类型:
| 警报编号 | 名称 | 说明 |
|---|---|---|
| 20 | bad_record_mac | 记录 MAC 校验失败,通常是密钥不匹配或数据被篡改 |
| 21 | decryption_failed | 解密失败(TLS 1.2 中已弃用,但有些实现仍会发) |
| 22 | record_overflow | 记录长度超过 2^14+2048 字节 |
| 40 | handshake_failure | 握手失败,比如没有共同的密码套件 |
| 42 | bad_certificate | 证书无效(过期、签名错误等) |
| 70 | protocol_version | 版本不兼容 |
我在调试时,最喜欢看警报消息。有一次设备连不上服务器,抓包发现服务器发了 handshake_failure(40)。查了半天,原来是设备只支持 RSA 密钥交换,但服务器只开了 ECDHE。改一下密码套件配置就好了。
bad_record_mac 和 handshake_failure 必须支持,否则出问题你都不知道为什么。
4.4 TLS 密码套件:加密算法的组合拳
密码套件决定了 TLS 连接用什么算法。TLS 1.2 的套件命名格式是:
TLS_密钥交换算法_认证算法_WITH_加密算法_消息认证码算法
举个例子:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- ECDHE:密钥交换使用椭圆曲线 Diffie-Hellman 临时密钥
- RSA:证书认证使用 RSA 签名
- AES_128_GCM:加密使用 128 位 AES 的 GCM 模式
- SHA256:消息认证码使用 SHA-256
常见的密码套件对比:
| 套件名称 | 密钥交换 | 加密 | MAC | 前向安全 | 嵌入式推荐度 |
|---|---|---|---|---|---|
| TLS_RSA_WITH_AES_128_CBC_SHA | RSA | AES-128-CBC | SHA-1 | 否 | 不推荐(无前向安全) |
| TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | ECDHE | AES-128-GCM | SHA-256 | 是 | 强烈推荐 |
| TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 | ECDHE | AES-128-GCM | SHA-256 | 是 | 推荐(需 ECC 证书) |
| TLS_PSK_WITH_AES_128_CCM | PSK | AES-128-CCM | CCM 内建 | 否 | 适合资源极受限设备 |
我个人习惯在嵌入式项目里优先用 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。为什么?
- ECDHE 提供前向安全性,即使私钥泄露,历史会话也无法解密
- AES-128-GCM 是 AEAD 算法,加密和认证一步到位,省掉了单独的 MAC 计算
- SHA-256 比 SHA-1 安全,而且很多 MCU 有硬件加速
TLS_RSA_WITH_AES_128_CBC_SHA。RSA 密钥交换没有前向安全性,而且 CBC 模式容易受 Padding Oracle 攻击。我见过一些老设备还在用这个套件,赶紧换掉。
如果设备资源实在紧张,比如只有几十 KB RAM,可以考虑 PSK 套件。PSK 不需要证书,双方预共享一个密钥就行。但 PSK 没有前向安全性,而且密钥管理是个麻烦事。
好了,TLS 1.2 的四个核心协议就讲到这里。记住:记录协议是基础,握手协议是核心,警报协议是调试利器,密码套件是安全基石。把这四个搞明白,TLS 1.2 你就掌握了八成。