密码套件的五脏六腑

一个密码套件到底是什么?你打开 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 画出来了。从上到下是握手流程,从左到右是数据流。你对照着看,会清楚很多。

密码套件五部件关系图 密钥交换算法 (ECDHE、DHE、RSA…) 认证算法 (RSA、ECDSA、EdDSA…) 预主密钥 → 主密钥 → 会话密钥 (通过 PRF 派生) 加密算法 (AES-GCM / ChaCha20…) MAC 算法 (HMAC-SHA256 / AEAD 合并…) 伪随机函数(PRF) PRF 供货所有派生

实际配置中的选择原则

了解了五个部件,接下来你面对的是几十个套件名。怎么挑选?我一般给团队定三条原则:

  1. AEAD 优先:只选 GCM、CCM、ChaCha20-Poly1305 结尾的套件。抛弃 CBC 和 NULL 加密。
  2. 前向安全优先:密钥交换必须是 DHE 或 ECDHE。如果你看到套件名里不含 DHE、ECDHE、PSK 等临时交换,直接划掉。
  3. 认证算法与证书匹配:你证书里是 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-Poly1305AEAD vs CBC
MAC数据完整性HMAC-SHA256(或AEAD内置)防篡改、防重放
PRF密钥派生TLS 1.2 HMAC-PRF、TLS 1.3 HKDF种子随机性、抵抗拓展攻击

好了,五个部件全过了一遍。记住一个核心思路:密码套件是组合拳,不能只看名字里某一段就认为安全。下次配置的时候,动手把每个算法拆开看看——你问我为什么不直接看官方推荐?嗯,因为很多厂商默认配的套件优先级不一定最优,手动优化一遍,心里才踏实。