密码套件的五脏六腑
一个密码套件到底是什么?你打开 Wireshark 看 TLS 握手,Client Hello 里那一串像 TLS_AES_128_GCM_SHA256 的东西,说白了就是一个算法套餐的编号。每一套套餐里都藏着五个核心部件——密钥交换算法、认证算法、加密算法、MAC 算法、伪随机函数。五样缺一不可,而且它们有严格的搭配逻辑。
我个人习惯把密码套件比作一个安全的「快递流程」:密钥交换是双方怎么安全地传递箱子密码;认证是确认对方不是冒牌货;加密是把货物锁进箱子里;MAC 是给箱子贴防拆封条;伪随机函数则是整个流程里所有派生密钥的「水源」。下面我一个一个拆开讲。
密钥交换算法 – 建立共享秘密
最先发生的就是密钥交换。握手开始时,客户端和服务器各自有一段随机数,但它们不能直接用,因为明文传出去就被窃听了。密钥交换算法的任务就是让双方在不安全的通道上,协商出一个只有双方知道的对称密钥(也叫预主密钥)。
关键点:密钥交换算法决定了你的前向安全性。我现在几乎只用 ECDHE;如果看到套件里是静态 DH 或传统 RSA 密钥交换,我会直接退避三舍。
常见的密钥交换算法:
| 算法 | 前向安全性 | 性能 | 推荐度 |
|---|---|---|---|
| RSA(传统) | ❌ 无 | 快 | 不推荐,已废弃 |
| DHE(临时 DH) | ✅ 有 | 较慢(大整数计算) | 可用,但 ECDHE 更好 |
| ECDHE(椭圆曲线临时 DH) | ✅ 有 | 快 | 强烈推荐 |
| PSK(预共享密钥) | 取决于模式 | 很快 | 特定场景(如 IoT) |
我以前维护过一个遗留 TLS 1.2 服务,里面配的是 TLS_RSA_WITH_AES_256_CBC_SHA。客户问「为什么你们的服务在 OpenSSL 1.1.1 上连不上?」我一看,RSA 密钥交换早被禁用了。后来改 ECDHE,问题解决——但顺便换掉了整套套件。
⚠️ 如果你还在使用 RSA 密钥交换(不包括证书签名),快点停掉。它不能提供前向安全性,一旦私钥泄露,所有历史流量都能被解密。
认证算法 – 你是谁?
认证算法负责验证通信对方的身份。TLS 里最常见的认证就是服务器出示 X.509 证书,客户端用证书里的公钥去验证握手签名。这里的签名算法就叫认证算法。
常见的认证算法:
- RSA 签名(
RSA-SHA256、RSA-PSS)—— 最广泛 - ECDSA 签名(
ECDSA-SHA256)—— 更小、更快的椭圆曲线版本 - EdDSA(Ed25519)—— 新一代方案,性能和抗侧信道都很好
- 匿名协商(
NULL)—— 不认证,仅用于内部测试或特殊场景
很多初学者会混淆「握手时使用的是 ECDHE 还是 ECDSA」这两个概念。ECDHE 负责密钥交换,ECDSA 负责认证,它们可以自由组合。ECDHE_ECDSA 表示「用 ECDHE 换密钥,用 ECDSA 认身份」;ECDHE_RSA 则表示「用 ECDHE 换密钥,用 RSA 认身份」。
💡 我个人推荐优先使用 ECDSA 证书。理由很简单:密钥更小,握手负载更低,尤其在高并发连接场景下优势明显。但要注意,ECDSA 需要私钥使用 P-256 或更高曲线,不支持过期曲线。
加密算法 – 给数据上锁
握手成功后,应用数据就要用加密算法保护了。加密算法分为两类:对称加密(流式 / 分组)和认证加密(AEAD)。现代 TLS 1.3 只允许 AEAD 加密算法,因为 AEAD 把加密和认证合并了。
常见的加密算法:
- AES_128_GCM —— 平衡速度和安全性,目前首选
- AES_256_GCM —— 更保守的安全余量(性能稍低)
- ChaCha20_Poly1305 —— 纯软件实现时比 AES 更快,移动端/嵌入式优选
- AES_128_CBC — 传统 CBC 模式,在 TLS 1.2 中仍存在,但需配合 HMAC 使用,不推荐
- ARIA_128_GCM、SM4_GCM — 针对特定地区标准
选加密算法时,我常和团队说「能用 GCM 就别碰 CBC」。为什么?CBC 模式需要单独的 HMAC 来做完整性校验,而且一旦实现有 bug(比如 OpenSSL 的 Lucky13 攻击),会被时序侧信道攻击。GCM 模式自带认证,一条指令完成加解密+完整性检查,还抗时序。
MAC 算法 – 防篡改的封印
在 AEAD 出现之前,TLS 通过单独的 MAC 算法来保证数据完整性。MAC 全称是消息认证码,用对称密钥计算一个「签名」,接收方重新算一遍,看是否一致。常见的 MAC 算法有 HMAC-SHA1、HMAC-SHA256、HMAC-SHA384 等。
在 TLS 1.2 的 CBC 套件里,加密和 MAC 是分开的:先 MAC 再加密(MAC-then-Encrypt)。这种顺序被证明有漏洞,比如 padding oracle 攻击。这也是 TLS 1.3 直接抛弃传统 MAC,强制使用 AEAD 的原因。
设计原则:不要自己拼 MAC + 加密的组合。如果必须兼容 TLS 1.2,优先选择 GCM 或 CCM 等 AEAD 算法,它们自带 MAC 功能。
对于遗留的 TLS 1.2 套件,比如 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,最后的 SHA256 就是指 MAC 算法。这个套件里 HMAC 用 SHA-256 计算。
伪随机函数 PRF – 派生一切密钥
最后一个成员,却是最关键的基础设施:伪随机函数(PRF)。当密钥交换算法协商出预主密钥后,PRF 负责把它扩展成多个会话密钥——加密密钥、MAC 密钥、IV、初始化向量等等。
你想想看,如果 PRF 弱,那么即使密钥交换再强,生成的密钥也可能被预测。PRF 在 TLS 1.2 里基于 HMAC,在 TLS 1.3 里基于 HKDF。具体来说:
- TLS 1.2 PRF:使用 HMAC 与特定的散列算法(如 SHA-256),经过两次迭代计算
- TLS 1.3 PRF:基于 HKDF-Extract 和 HKDF-Expand,更简洁也更安全
举个例子,握手最后阶段,PRF 需要生成 4 个密钥:客户端写加密密钥、客户端写 IV、服务器写加密密钥、服务器写 IV,可能还有各自的 HMAC 密钥。这些全部靠 PRF 从主密钥(master secret)中推导出来。
⚠️ 之前我在一个定制嵌入式 TLS 栈的项目里,看到开发人员为了省代码,直接硬编码了 PRF 的 seed。后果就是所有连接的会话密钥完全相同。嗯,这比不加密还危险。
五者的关系——一张图说清楚
我把密码套件的五个部件之间的关系用一张 SVG 画出来了。从上到下是握手流程,从左到右是数据流。你对照着看,会清楚很多。
实际配置中的选择原则
了解了五个部件,接下来你面对的是几十个套件名。怎么挑选?我一般给团队定三条原则:
- AEAD 优先:只选 GCM、CCM、ChaCha20-Poly1305 结尾的套件。抛弃 CBC 和 NULL 加密。
- 前向安全优先:密钥交换必须是 DHE 或 ECDHE。如果你看到套件名里不含
DHE、ECDHE、PSK等临时交换,直接划掉。 - 认证算法与证书匹配:你证书里是 RSA 公钥,就不能配 ECDSA 套件。同理,如果你是 ECC 证书却只开了 RSA 套件,握手会失败。
曾经有一次排查一个跨国设备的 TLS 兼容性问题,发现对方只支持 TLS_RSA_WITH_AES_256_CBC_SHA,而我们只开 ECDHE。协商不了。最后我们在服务端额外加了个 ECDHE_RSA 套件,并配了 RSA 证书——系统兼容了,但安全上我们限定了 ECDHE 不走静态 RSA 交换。
一张表回顾五部件
| 部件 | 作用阶段 | 典型算法 | 安全关注点 |
|---|---|---|---|
| 密钥交换 | 握手前中期 | ECDHE、DHE、PSK | 前向安全性 |
| 认证 | 握手中期 | RSA、ECDSA、EdDSA | 签名强度、曲线选择 |
| 加密 | 数据传输 | AES-GCM、ChaCha20-Poly1305 | AEAD vs CBC |
| MAC | 数据完整性 | HMAC-SHA256(或AEAD内置) | 防篡改、防重放 |
| PRF | 密钥派生 | TLS 1.2 HMAC-PRF、TLS 1.3 HKDF | 种子随机性、抵抗拓展攻击 |
好了,五个部件全过了一遍。记住一个核心思路:密码套件是组合拳,不能只看名字里某一段就认为安全。下次配置的时候,动手把每个算法拆开看看——你问我为什么不直接看官方推荐?嗯,因为很多厂商默认配的套件优先级不一定最优,手动优化一遍,心里才踏实。