在对称加密的世界里,发送方与接收方必须共享同一把密钥才能完成加密和解密。然而,在开放的互联网上,两个素未谋面的终端如何在敌手监听一切流量的前提下协商出一个只有彼此知晓的共享秘密?这一问题的优雅解答,便是密钥交换(key exchange)协议。从 1976 年 Diffie 与 Hellman 在经典论文中提出的离散对数方案,到今天在浏览器、即时通信、物联网设备中每秒发生数十亿次的椭圆曲线 Diffie-Hellman 握手,密钥交换始终是公钥密码学最核心的应用场景之一。本文将围绕当前最主流的椭圆曲线密钥交换机制展开,涵盖 ECDH 的基本原理、X25519 与 X448 的设计哲学、临时密钥带来的前向保密特性,以及 TLS 1.3、Signal X3DH、MLS TreeKEM 等现实协议中的工程实践,最后展望后量子时代的混合密钥交换方向。
先用一张图把本文的核心关系串起来。
sequenceDiagram
participant A as 发起方
participant B as 响应方
A->>B: 临时公钥
B->>A: 临时公钥
A->>A: 共享秘密 + KDF
B->>B: 共享秘密 + KDF
一、ECDH:椭圆曲线上的密钥交换
椭圆曲线 Diffie-Hellman(Elliptic Curve Diffie-Hellman,ECDH)是经典 DH 协议在椭圆曲线群上的自然推广。其核心数学极为简洁:设 G 为椭圆曲线上一个已知的基点(base point),阶为素数 n。Alice 随机选取私钥 a (1 < a < n),计算公钥 A = aG 并发送给 Bob;Bob 同样随机选取私钥 b,计算公钥 B = bG 并发送给 Alice。此后,双方各自执行一次标量乘法(scalar multiplication):Alice 计算 aB = a(bG) = abG,Bob 计算 bA = b(aG) = abG。由于椭圆曲线离散对数问题(ECDLP)的困难性,窃听者即使截获 A 和 B,也无法在计算上可行的时间内恢复 a 或 b,因此无法推导出共享秘密 abG。
需要特别注意的是,ECDH 的原始输出是一个椭圆曲线上的点,其坐标的比特分布并不均匀,不能直接用作对称密钥。工程实践中必须将该点的 x 坐标通过密钥派生函数(Key Derivation Function,KDF)提取为固定长度的均匀随机比特串。常见的做法是使用 HKDF(基于 HMAC 的 KDF)对 x 坐标进行 Extract-then-Expand 处理,从而得到适合 AES-256-GCM 或 ChaCha20-Poly1305 等对称算法使用的密钥材料。忽略 KDF 步骤是一个常见的实现错误,可能导致密钥偏差(key bias),从而削弱整体安全性。
另一个容易被忽视的细节是辅因子(cofactor)问题。并非所有椭圆曲线的阶都恰好是素数,很多标准曲线的阶等于素数 n 乘以一个小的辅因子 h。如果 h > 1,攻击者可以发送一个属于小子群(small subgroup)的点,诱导受害方的共享秘密落入一个可预测的小集合中,从而实现小子群攻击(small-subgroup attack)。辅因子 DH(cofactor DH)通过在标量乘法中额外乘以 h 来消除这一风险,即计算 shared = h · a · B。X25519 的设计从根本上规避了这一问题,这是它被广泛推荐的重要原因之一。
二、X25519
X25519 是由 Daniel J. Bernstein 设计的基于 Curve25519 的密钥交换函数,其标准化文档为 RFC 7748。Curve25519 是一条定义在素数域 GF(2^255 - 19) 上的 Montgomery 曲线,方程形式为 y^2 = x^3 + 486662x^2 + x。该曲线的阶为 8n,其中 n 是一个 253 比特的大素数,辅因子 h = 8。X25519 函数仅操作 x 坐标,输入一个 32 字节的标量和一个 32 字节的 u 坐标,输出一个 32 字节的 u 坐标结果。
X25519 的核心计算是 Montgomery ladder(蒙哥马利阶梯)算法。与传统的双倍-加(double-and-add)标量乘法不同,Montgomery ladder 在每一步中同时维护两个点 (R0, R1),无论当前比特是 0 还是 1,都执行相同的一次倍点(doubling)和一次差分加法(differential addition)操作,只是操作的目标不同。这一结构天然地保证了常量时间(constant-time)执行——无论私钥的比特模式如何,算法的控制流、内存访问模式和执行时间都是相同的,从而从根本上免疫了基于时序的侧信道攻击(timing side-channel attack)。
以下简化代码展示了 Montgomery ladder 的核心结构:
#include <stdint.h>
#include <string.h>
typedef uint64_t fe25519[5]; /* 五肢表示,每肢 51 比特 */
/* 条件交换:若 swap == 1 则交换 a 与 b,否则不动;常量时间 */
static void cswap(fe25519 a, fe25519 b, uint64_t swap) {
for (int i = 0; i < 5; i++) {
uint64_t mask = 0 - swap; /* 全 0 或全 1 */
uint64_t t = mask & (a[i] ^ b[i]);
a[i] ^= t;
b[i] ^= t;
}
}
/*
* 简化的 X25519 标量乘法骨架
* scalar: 32 字节钳位后的私钥
* u_in : 32 字节的 u 坐标(对端公钥或基点 9)
* u_out : 32 字节的结果
*/
void x25519_scalarmult(uint8_t u_out[32],
const uint8_t scalar[32],
const uint8_t u_in[32])
{
fe25519 x1, x2, z2, x3, z3;
fe_frombytes(x1, u_in); /* 将输入 u 解码到域元素 */
fe_one(x2); /* x2 = 1 */
fe_zero(z2); /* z2 = 0 => (x2:z2) = 无穷远点 */
fe_copy(x3, x1); /* x3 = u */
fe_one(z3); /* z3 = 1 => (x3:z3) = u */
uint64_t prev_bit = 0;
/* 从最高有效比特到第 0 比特迭代 */
for (int i = 254; i >= 0; i--) {
uint64_t bit = (scalar[i >> 3] >> (i & 7)) & 1;
uint64_t swap = bit ^ prev_bit;
prev_bit = bit;
cswap(x2, x3, swap);
cswap(z2, z3, swap);
/*
* Montgomery ladder 核心步骤(省略域运算细节):
* (x2, z2) = double(x2, z2)
* (x3, z3) = diffadd(x2, z2, x3, z3, x1)
* 此处实际包含加法、减法、乘法、平方等域操作,
* 且均在 GF(2^255-19) 上以常量时间完成。
*/
montgomery_ladderstep(x1, x2, z2, x3, z3);
}
cswap(x2, x3, prev_bit);
cswap(z2, z3, prev_bit);
/* 最终求逆:u_result = x2 * z2^{-1} mod p */
fe25519 zinv;
fe_invert(zinv, z2); /* 费马小定理求逆 */
fe_mul(x2, x2, zinv);
fe_tobytes(u_out, x2); /* 编码为 32 字节小端序 */
}上述代码中,cswap
是安全实现的关键——它在不暴露 swap
值的前提下有条件地交换两个域元素。整个循环体内没有任何依赖于秘密比特的分支或变址操作,因此在正确实现的情况下,任何侧信道观测者都无法从执行轨迹中提取私钥信息。
X25519 要求在使用私钥之前对其进行钳位(clamping)操作:将最低 3 比特清零(确保标量是 8 的倍数,消除小子群信息泄露),将第 255 比特清零(确保标量小于 2^255),并将第 254 比特置 1(保证 Montgomery ladder 的迭代从固定位置开始,进一步强化常量时间)。经过钳位处理后,即使实现者犯了某些微小的错误,协议依然能保持安全性——这是 Bernstein 在设计时”误用抵抗(misuse resistance)“理念的集中体现。
X25519 之所以成为现代密钥交换的事实标准,原因可以概括为以下几点:第一,Curve25519 的参数选择完全透明(rigidly generated),不存在后门疑虑;第二,Montgomery 曲线形式与 ladder 算法的结合使得常量时间实现异常自然;第三,仅操作 x 坐标使得编解码简单且紧凑(公钥和共享秘密均为 32 字节);第四,在现代 64 位处理器上,一次 X25519 操作仅需约 15 万个时钟周期,性能优异。
三、X448
X448 是 RFC 7748 中与 X25519 并列定义的另一个密钥交换函数,基于 Mike Hamburg 设计的 Goldilocks 曲线(Curve448-Goldilocks)。该曲线定义在素数域 GF(2^448 - 2^224 - 1) 上,这个素数的特殊形式使得模运算可以高效地利用 Karatsuba 乘法分解为两个 224 比特的半字运算。Curve448 的辅因子为 4,基点阶为一个 446 比特的大素数,提供约 224 比特的安全强度(security level)。
X448 的设计哲学与 X25519 完全一致:Montgomery 曲线、仅操作 u 坐标、常量时间 ladder、钳位处理。不同之处在于安全级别的提升——X25519 提供约 128 比特安全强度,而 X448 提供约 224 比特,与 SHA-3 和 AES-256 的安全级别对齐。当系统需要满足更高的安全边际要求,或者其安全性分析假设量子计算机在生命周期内可能削弱 128 比特方案时,X448 是合理的选择。
然而,在绝大多数实际场景中,X25519 已经提供了足够的安全裕度,且速度更快、实现更广泛。TLS 1.3 同时支持 x25519 和 x448 两个命名群组(named group),但在实际部署中,x25519 的使用率远超 x448。选择 X448 通常出于合规性需求(如某些政府或国防场景要求至少 192 比特安全强度)或纵深防御(defense in depth)的考量。
四、ECDHE:从静态密钥到临时密钥
在静态 ECDH(static ECDH)中,通信双方使用长期不变的密钥对参与密钥交换。例如,服务器在证书中绑定一把 ECDH 公钥 S = sG,每次握手都用同一把私钥 s 计算共享秘密。这种方式存在根本性缺陷:一旦长期私钥 s 在未来任意时刻被泄露——无论是通过物理攻击、软件漏洞还是法律强制手段——攻击者便可用 s 与此前记录的所有客户端公钥重新计算每一次会话的共享秘密,从而解密全部历史流量。静态密钥如同一把万能钥匙:丢失它意味着所有曾经锁上的门都可以被打开。
ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)的核心改进是:每次会话都生成全新的临时密钥对(ephemeral key pair),会话结束后立即销毁私钥。完整的 ECDHE 协议流程如下:
Alice Bob
| |
| 1. 生成临时私钥 a ←$ [1, n-1] |
| 计算临时公钥 A = aG |
| |
| A |
| ------------------------------------------------>
| |
| 2. 生成临时私钥 b ←$ [1, n-1] |
| 计算临时公钥 B = bG |
| |
| B |
| <------------------------------------------------
| |
| 3. S = a · B = a(bG) = abG |
| 4. S = b · A = b(aG) = abG |
| |
| 5. key = HKDF(salt, S.x, info, len) |
| 6. key = HKDF(salt, S.x, info, len)
| |
| 7. 安全擦除 a 8. 安全擦除 b |
步骤 5 至关重要。原始共享秘密 S 是椭圆曲线上的点,其 x 坐标的比特分布不均匀,不能直接用作对称密钥。HKDF 的 Extract 阶段将 S.x 与盐值混合生成伪随机密钥(PRK);Expand 阶段再从 PRK 派生出固定长度的对称密钥材料,适用于 AES-256-GCM 或 ChaCha20-Poly1305 等算法。跳过 HKDF 直接使用 S.x 是一个常见的实现错误,会引入密钥偏差(key bias)。
前向保密。 前向保密(Forward Secrecy,FS,也称完美前向保密 PFS)的定义是:即使长期密钥在未来被泄露,此前已完成的会话密钥仍然安全。在 ECDHE 中,每次握手的共享秘密 abG 完全取决于当次随机生成的临时标量 a 和 b。会话结束后 a 和 b 被安全擦除,攻击者即使日后获得了服务器的长期签名密钥(用于身份认证),也无法重建任何历史会话的 abG——每次握手在数学上完全独立。前向保密对抵抗“先记录,后解密”(harvest now, decrypt later)的大规模被动监控尤为重要:ECDHE 使得每一条记录下来的密文都需要独立破解,从根本上瓦解了批量追溯解密的攻击策略。
与传统 FFDHE 的比较。 经典有限域 Diffie-Hellman(Finite Field DHE,FFDHE)在乘法群 Z_p* 上执行相同的协议:Alice 发送 g^a mod p,Bob 发送 g^b mod p,共享秘密为 g^(ab) mod p。ECDHE 相对于 FFDHE 有三个显著优势。第一,同等安全强度下密钥更短:128 比特安全强度仅需 256 比特的椭圆曲线标量,而 FFDHE 需要 3072 比特的素数 p。第二,不存在已知的亚指数攻击:FFDHE 面临数域筛法(Number Field Sieve)的威胁,而椭圆曲线上的最优攻击仍是完全指数级的 Pollard rho 算法。第三,预计算攻击对 ECDHE 无效:在 FFDHE 中若多个服务器共用同一素数 p,攻击者可对 p 进行一次性预计算(如 Logjam 攻击)后低成本攻破所有相关连接;ECDHE 不存在类似的预计算加速路径。TLS 1.3 正是基于这些考量,将 x25519 等 ECDHE 群组列为优先推荐。
从工程角度看,前向保密是密码学领域中“正确的理念被采纳得太晚”的典型案例。Diffie 和 Hellman 在 1976 年的原始论文中就隐含了临时密钥的思想,学术界在 1990 年代已清楚认识到前向保密的重要性,但整个行业花了将近二十年才将其变成默认配置。推动 TLS 1.3 将前向保密设为强制要求,是过去十年互联网安全领域最有价值的单一决策之一——它将一个本应从一开始就成为默认选项的安全属性,终于变成了不可协商的基线。
五、前向保密的工程意义
前向保密从理论概念走向工程必需品的转折点,可以追溯到两个标志性事件。
第一个事件是 2013 年的 Snowden 泄露。公开文件揭示了部分情报机构确实在实施大规模流量记录计划,并试图通过获取服务器私钥来批量解密历史通信。这一消息直接推动了互联网工程界对前向保密的紧迫认知。此前,许多主流网站出于性能考虑仍在使用 RSA 密钥传输(RSA key transport)模式——客户端生成随机预主秘密(pre-master secret),用服务器的 RSA 公钥加密后发送。这种模式不提供前向保密,因为服务器 RSA 私钥的泄露将导致所有历史会话的预主秘密被直接解密。Snowden 事件后,各大互联网公司迅速将其服务器配置切换至 ECDHE 套件优先。
第二个事件是 2014 年的 Heartbleed 漏洞(CVE-2014-0160)。这个 OpenSSL 的内存越界读取漏洞允许攻击者远程读取服务器内存中的敏感数据,包括可能存在的服务器私钥。对于使用 RSA 密钥传输且没有前向保密的网站,Heartbleed 意味着不仅当前通信被窃取,此前所有被记录的流量也可以被追溯解密。而对于配置了 ECDHE 的网站,Heartbleed 造成的损害被限制在攻击者主动利用漏洞的时间窗口内——历史会话的临时密钥早已不在内存中,因此无法被读取。这两个事件的叠加效应彻底改变了行业共识:前向保密不再是可选的安全增强,而是基本的安全底线。
在实际工程中,要完整实现前向保密的承诺,还需要注意会话恢复(session resumption)机制的处理。TLS 协议支持会话票证(session ticket)机制以避免每次连接都进行完整握手。然而,如果用于加密会话票证的密钥(Session Ticket Encryption Key,STEK)长期不轮换,那么 STEK 的泄露同样会破坏前向保密。因此,最佳实践要求 STEK 定期轮换(通常建议每小时到每天),旧密钥在轮换后立即从内存中安全擦除。部分高安全性部署甚至完全禁用会话票证,转而使用基于服务端状态的会话 ID 恢复。
六、TLS 1.3 中的密钥交换
TLS 1.3(RFC 8446)在密钥交换设计上做出了几项关键性的简化和安全强化。
最重要的改变是完全移除了 RSA 密钥传输模式。在 TLS 1.2 及更早版本中,客户端可以使用服务器的 RSA 证书公钥直接加密预主秘密,这种模式虽然简单,但不提供前向保密。TLS 1.3 强制要求所有密钥交换必须通过 (EC)DHE 完成,从而在协议层面保证了前向保密。这一决定在标准化过程中引发了激烈讨论——部分企业希望保留 RSA 密钥传输以便于网络监控和调试——但最终安全性考量占据了上风。
在 TLS 1.3 握手流程中,客户端在 ClientHello 消息中通过
key_share 扩展发送一个或多个临时公钥(通常包含
x25519 和/或 secp256r1
群组的密钥份额)。服务器从中选择一个支持的群组,在
ServerHello 中回复对应的
key_share,双方即可立即计算出共享秘密。与 TLS
1.2 需要额外的 ServerKeyExchange 消息相比,TLS 1.3
将密钥交换压缩到了前两条消息中,实现了
1-RTT(一个往返时间)完成握手——甚至在会话恢复场景下支持
0-RTT(零往返时间)握手。
下面用一个简化的序列图来展示 TLS 1.3 的 1-RTT 握手流程,着重标注密钥交换的关键步骤:
客户端 (Client) 服务器 (Server)
| |
| ClientHello |
| + supported_versions: TLS 1.3 |
| + key_share: x25519 临时公钥 gx |
| + signature_algorithms |
| + supported_groups: x25519, secp256r1 |
|--------------------------------------------------->|
| |
| ServerHello |
| + key_share: x25519 临时公钥 gy |
|<---------------------------------------------------|
| |
| --- 此刻双方计算共享秘密 S = gxy --- |
| --- HKDF-Extract(S) → Handshake Secret --- |
| |
| {EncryptedExtensions} |
| {Certificate} |
| {CertificateVerify} |
| {Finished} |
|<---------------------------------------------------|
| |
| {Finished} |
|--------------------------------------------------->|
| |
| === 握手完成,派生 Application Traffic Keys === |
| |
| [Application Data] <=========================> |
| (使用派生的对称密钥加密) |
关键要点:密钥交换在前两条消息(ClientHello / ServerHello)中就已完成,从 EncryptedExtensions 开始的所有握手消息都已经使用从共享秘密派生的密钥加密传输。这意味着服务器的证书信息在传输中是加密的——相比 TLS 1.2 中证书明文传输的做法,这是一个重要的隐私改进。花括号 {} 表示加密传输的消息,方括号 [] 表示应用层加密数据。
TLS 1.3 目前定义的命名群组包括 x25519、x448、secp256r1、secp384r1 和 secp521r1 等。在实际部署中,x25519 因其性能优势和安全设计已成为绝对主流。根据公开的遥测数据,在 TLS 1.3 连接中 x25519 的使用比例超过 90%。值得注意的是,TLS 1.3 的设计为未来的密钥交换算法预留了扩展空间,这对后量子迁移至关重要——后续的 RFC 9180(HPKE)和正在推进的混合密钥交换草案已经在利用这一扩展机制。
TLS 1.3 的密钥调度(key schedule)使用了一个精心设计的 HKDF 派生链。握手过程中的共享秘密首先通过 HKDF-Extract 与预共享密钥(PSK,如果存在)结合生成早期秘密(Early Secret),再进一步派生出握手秘密(Handshake Secret)和主秘密(Master Secret)。每个阶段的密钥都是独立派生的,这意味着即使某一层级的密钥被泄露,其他层级的密钥仍然安全。
七、Signal X3DH
Signal 协议中的 X3DH(Extended Triple Diffie-Hellman)是为异步通信场景设计的密钥交换协议。在即时通信应用中,发送方和接收方往往不同时在线——Alice 想给 Bob 发消息时,Bob 可能处于离线状态。传统的交互式 DH 握手要求双方实时交换消息,无法适用于这种场景。X3DH 通过预密钥包(prekey bundle)机制优雅地解决了这一问题。
协议涉及以下密钥(小写为私钥,大写为对应公钥,G 为基点):
- Alice 的长期身份密钥:ik_A,IK_A = ik_A · G
- Alice 的临时密钥:ek_A,EK_A = ek_A · G(每次会话新生成)
- Bob 的长期身份密钥:ik_B,IK_B = ik_B · G
- Bob 的签名预密钥:spk_B,SPK_B = spk_B · G(定期轮换,用 ik_B 签名)
- Bob 的一次性预密钥:opk_B,OPK_B = opk_B · G(每个仅使用一次)
Bob 预先将 IK_B、SPK_B、Sig(ik_B, SPK_B) 以及一批 OPK_B 上传至服务器。当 Alice 要向离线的 Bob 发起会话时,她从服务器获取 Bob 的预密钥包,验证签名后生成临时密钥对 (ek_A, EK_A),然后执行以下四次 DH 运算:
DH1 = ik_A · SPK_B = ik_A · spk_B · G
DH2 = ek_A · IK_B = ek_A · ik_B · G
DH3 = ek_A · SPK_B = ek_A · spk_B · G
DH4 = ek_A · OPK_B = ek_A · opk_B · G (若 OPK 可用)
SK = HKDF( DH1 ‖ DH2 ‖ DH3 ‖ DH4 )
四次 DH 的组合提供了精心设计的安全属性,每一次运算都有不可替代的作用:
- DH1 = ik_A · SPK_B — Bob 认证 Alice 的身份。只有持有 ik_A 的人才能计算此值;Bob 上线后用 spk_B 验证对方确实是 Alice。
- DH2 = ek_A · IK_B — Alice 认证 Bob 的身份。只有持有 ik_B 的人才能计算此值;确保预密钥包确实属于 Bob 而非冒充者。
- DH3 = ek_A · SPK_B — 提供前向保密。即使 ik_A 和 ik_B 日后都被泄露,只要临时密钥 ek_A 已被擦除,SK 就无法被恢复——因为 DH3 中包含了仅存在于本次会话的 ek_A。
- DH4 = ek_A · OPK_B — 提供一次性保护。每个 OPK 仅使用一次后即从服务器删除,防止攻击者以旧的预密钥包重放 Alice 的初始消息。
DH1 与 DH2 的组合实现了双向身份认证(mutual authentication)——即使 Alice 单方面完成了密钥协商,Bob 上线后仍然可以确认对方身份。X3DH 还提供了可否认性(deniability):DH 协议的对称性意味着 Alice 能计算出的结果 Bob 也能独立计算,第三方无法判断共享秘密的生成究竟涉及了哪一方的实际参与,因此任何一方都无法向第三方证明通信确实发生过。
下面的完整流程图展示了 Alice 向离线 Bob 发送第一条加密消息的全过程:
Bob (离线) 服务器 Alice (在线)
| | |
| 上传预密钥包: | |
| · IK_B | |
| · SPK_B + Sig(ik_B, SPK_B) |
| · OPK_B1, OPK_B2, ... | |
|-------------------------->| |
| | |
| | 请求 Bob 的预密钥包 |
| |<--------------------------|
| | |
| | 返回: IK_B, SPK_B, |
| | Sig, OPK_B1 |
| |-------------------------->|
| | |
| | 验证 Sig(ik_B, SPK_B) |
| | 生成临时密钥 (ek_A, EK_A)|
| | |
| | DH1 = ik_A · SPK_B |
| | DH2 = ek_A · IK_B |
| | DH3 = ek_A · SPK_B |
| | DH4 = ek_A · OPK_B1 |
| | SK = HKDF(DH1‖DH2‖DH3‖DH4)
| | |
| | 初始消息: |
| | · IK_A |
| | · EK_A |
| | · 使用的 OPK 标识 |
| | · AEAD-Enc(SK, 明文) |
| <-----------------------|<--------------------------|
| | 服务器删除 OPK_B1 |
| | |
| Bob 上线后: | |
| 从消息中取出 IK_A, EK_A | |
| 用 ik_B, spk_B, opk_B1 | |
| 重算四次 DH: | |
| DH1 = spk_B · IK_A | |
| DH2 = ik_B · EK_A | |
| DH3 = spk_B · EK_A | |
| DH4 = opk_B · EK_A | |
| SK = HKDF(DH1‖DH2‖DH3‖DH4) |
| AEAD-Dec(SK, 密文) → 明文 | |
| 安全删除 opk_B1 | |
关键要点:Alice 在 Bob 完全离线的情况下,仅凭服务器上的预密钥包就完成了密钥协商并发出了第一条加密消息。Bob 上线后利用自己的私钥材料独立重算相同的四次 DH——注意 DH 的交换律保证双方得到相同的结果(例如 ik_A · SPK_B = ik_A · spk_B · G = spk_B · ik_A · G = spk_B · IK_A)——从而恢复出同一个 SK 并解密消息。整个过程无需任何实时交互。
X3DH 完成初始密钥建立后,后续消息使用 Double Ratchet(双棘轮)算法进行密钥演进,在每条消息级别实现前向保密和入侵后恢复(post-compromise security)。X3DH 与 Double Ratchet 的组合构成了 Signal 协议的核心,被 WhatsApp、Google Messages 等主流通信应用广泛采用。
八、群组密钥交换与 TreeKEM
将双方密钥交换扩展到多方(群组)场景是一个显著更困难的问题。朴素的方案是让群组中的每一对成员独立执行 ECDHE,但这导致密钥交换的次数随群组规模呈 O(n^2) 增长,对于数百甚至数千人的大群组完全不可行。
消息层安全协议(Messaging Layer Security,MLS,RFC 9420)通过 TreeKEM 方案解决了这一问题。TreeKEM 的核心思想是将群组成员组织为一棵左平衡二叉树(left-balanced binary tree),每个叶子节点对应一个群组成员,每个内部节点持有一个密钥对。下面用一个 5 人群组来演示 TreeKEM 的完整工作机制。
初始建树。 Alice、Bob、Carol、Dave、Eve 作为叶子节点排列,构成如下左平衡二叉树。每个叶子持有该成员自己的密钥对,每个内部节点的秘密值由其子节点的秘密通过 KDF 派生:
[root]
(sr, PKr)
/ \
[N1] [N2]
(s1, PK1) (s2, PK2)
/ \ / \
[N3] Carol Dave Eve
(s3, PK3) (c, C) (d, D) (e, E)
/ \
Alice Bob
(a, A) (b, B)
每个成员知道从自己的叶子到根的路径上所有节点的秘密值。例如 Alice 知道 a、s3、s1、sr;Carol 知道 c、s1、sr;Dave 知道 d、s2、sr。根节点的秘密 sr 是全体成员的共享秘密,用于派生群组加密密钥。
密钥更新(Update)。 当 Alice 需要更新密钥时,她沿自己的路径(Alice → N3 → N1 → root)生成新的秘密值,并为路径上每个节点的共路径(co-path)节点加密新密钥。共路径是路径上每个节点的兄弟节点:
Alice 的路径: Alice → N3 → N1 → root
Alice 的共路径: Bob(N3 的兄弟), Carol(N1 中 N3 的兄弟), N2(root 中 N1 的兄弟)
Alice 执行:
1. 生成新叶子秘密 a',派生新 N3 秘密: s3' = KDF(a')
2. 派生新 N1 秘密: s1' = KDF(s3')
3. 派生新 root 秘密: sr' = KDF(s1')
4. 发送 Commit 消息:
· Enc(B, s3') → Bob 用私钥 b 解密,获得 s3',再派生 s1', sr'
· Enc(C, s1') → Carol 用私钥 c 解密,获得 s1',再派生 sr'
· Enc(PK2, sr') → Dave 和 Eve 共享 N2 的私钥 s2,用 s2 解密获得 sr'
整个更新只需 3 条加密消息——等于树的高度 O(log n)——而非向每个成员单独发送。每个成员解密与自己共路径位置对应的那一条密文,然后沿路径向上派生即可获得新的根秘密。
成员移除(Remove)。 假设 Bob 被移除出群组。发起移除的成员(假设是 Alice)执行以下操作:
1. 将 Bob 的叶子节点标记为空白(blank)
2. 清除 Bob 所在路径上所有内部节点的秘密值(N3, N1, root 均被置空)
[root] ← 已清空
/ \
[N1] [N2] ← N2 不受影响
(空) (s2, PK2)
/ \ / \
[___] Carol Dave Eve
(空) (c, C) (d, D) (e, E)
/ \
Alice (空) ← Bob 已移除
(a, A)
3. Alice 作为提交者,为自己的路径生成全新秘密:
a'' → s3'' = KDF(a'') → s1'' = KDF(s3'') → sr'' = KDF(s1'')
4. 发送 Commit 消息(只需加密给共路径上仍存在的节点):
· Enc(C, s1'') → Carol 解密获得 s1'',派生 sr''
· Enc(PK2, sr'') → Dave、Eve 通过 N2 私钥解密获得 sr''
注意 N3 的共路径兄弟(Bob)已被移除,因此不需要为该层级加密。Bob 不持有任何新秘密值,无法计算 sr’’,从而被彻底排除在新纪元之外。
成员添加(Add)。 假设 Frank 加入群组。添加操作将 Frank 作为新叶子插入树的最右位置,同时创建一个新的内部节点:
[root']
/ \
[N1] [N4] ← 新内部节点
(s1, PK1) (s4, PK4)
/ \ / \
[N3] Carol [N2] Frank
(s3, PK3) (c, C) (s2, PK2) (f, F)
/ \ / \
Alice Bob Dave Eve
(a, A) (b, B) (d, D) (e, E)
添加者(假设是 Alice)在 Commit 中向 Frank 发送必要的群组状态信息(Welcome 消息)。Frank 收到后初始化自己的路径秘密,并向共路径节点加密新路径密钥,使全体成员进入新纪元。
安全属性。 TreeKEM 基于纪元(epoch)的设计提供了清晰的安全边界。每次 Commit(无论是更新、移除还是添加)都产生一个新纪元,旧纪元的密钥材料可以立即擦除,实现前向保密。此外,MLS 提供入侵后安全(post-compromise security):即使某个成员的设备被完全入侵,只要该成员随后执行一次 Update 操作引入新的随机性,群组秘密就会恢复为攻击者不可知的状态。
TreeKEM 的工程挑战在于并发处理——当多个成员同时提交更新时,需要一个明确的排序和冲突解决机制。MLS 通过交付服务(Delivery Service)对 Commit 消息进行全局排序来解决这一问题。此外,大量移除操作可能导致树的不平衡,需要周期性的树压缩(tree compaction)来恢复 O(log n) 的路径长度。从草案到正式标准(RFC 9420)经历了六年的设计迭代,这恰恰说明群组密钥管理的真正难题不在于渐近复杂度,而在于如何在成员动态变化、网络异步、设备异构的现实环境中可靠地维护一棵全局一致的密钥树。
九、密钥交换的未来
密钥交换领域面临的最大挑战来自量子计算。Shor 算法在理论上可以在多项式时间内解决椭圆曲线离散对数问题,这意味着一台足够强大的量子计算机将使 ECDH、X25519 和 X448 全部失效。虽然这样的量子计算机尚未出现,但”先记录,后解密”的威胁模型要求我们现在就开始部署后量子(post-quantum)密钥交换方案。
NIST 于 2024 年正式发布了 ML-KEM(Module-Lattice-based Key Encapsulation Mechanism,原名 Kyber)标准(FIPS 203)。ML-KEM 基于模格(module lattice)上的学习误差(Learning With Errors,LWE)问题,被认为对量子计算机具有抵抗力。然而,ML-KEM 作为一项相对年轻的方案,其安全性尚未经历像 ECDH 那样长达数十年的密码分析检验。因此,当前的主流策略是混合密钥交换(hybrid key exchange)——将传统的 X25519 与后量子的 ML-KEM-768 组合使用,两者的共享秘密通过 KDF 合并。只有当两者同时被攻破时,最终密钥才会泄露。
混合密钥交换的具体做法是在 TLS 1.3 的
key_share
扩展中定义新的混合命名群组。例如,Google Chrome 和
Cloudflare 已经在实际部署中实验了 X25519Kyber768Draft00
等混合群组。客户端在 ClientHello 中同时发送 X25519 和
ML-KEM-768 的公钥(合并为一个
key_share),服务器同样回复两种算法的响应,双方分别计算两个共享秘密后拼接并通过
HKDF
派生最终密钥。这种方案的代价是握手消息体积增大(ML-KEM-768
的公钥约 1184 字节,密文约 1088 字节,远大于 X25519 的 32
字节),但对于大多数网络环境而言,这一额外开销是可以接受的。
在性能方面,ML-KEM 的密钥生成和封装/解封操作的速度实际上与 X25519 处于同一数量级,甚至在某些平台上更快。真正的性能瓶颈在于带宽——对于移动网络和物联网设备,更大的握手消息可能导致额外的网络往返。TLS 1.3 的分割 ClientHello(split ClientHello)机制和证书压缩(certificate compression)等技术可以在一定程度上缓解这一问题。
除了 ML-KEM,NIST 还在推进基于同源(isogeny)的 SIKE 替代方案的研究(SIKE 已被攻破),以及基于编码(code-based)的方案如 Classic McEliece(密钥极大但密文紧凑)。未来的密钥交换生态可能需要支持多种后量子算法的灵活协商能力,HPKE(Hybrid Public Key Encryption,RFC 9180)和 MLS 协议的可扩展设计为此提供了良好的框架。
展望更远的未来,密钥交换可能进入”密码敏捷性(crypto agility)“的新时代——协议和实现层面必须能够快速切换底层算法,以应对新的密码分析突破或量子计算的实际进展。这要求密钥交换的 API 设计保持抽象,参数协商机制保持灵活,密钥材料的生命周期管理保持严格。从 Diffie-Hellman 1976 年的开创性论文到今天的后量子混合方案,密钥交换的核心问题——如何在不安全的信道上建立安全的共享秘密——始终没有改变,改变的只是我们对”不安全”和”安全”的定义边界。
密码学百科系列 · 第 19 篇
← 上一篇:数字签名 | 系列目录 | 下一篇:混合加密与 KEM/DEM →
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【密码学百科】安全信道构造:Noise 协议框架与 Signal 协议
现代安全通信协议如何实现认证性、机密性与前向保密——本文深入 Noise 协议框架的握手模式记号,剖析 Signal 的 X3DH 与 Double Ratchet,以及 WireGuard 和 MLS 的设计
【国密算法与国密 TLS 系列】SM2 vs ECDSA/X25519:椭圆曲线的国产方案到底怎么样
从曲线参数、签名/加密/密钥交换算法、标量乘法实现到侧信道防护,全方位对比 SM2、P-256 和 Curve25519 三条主流椭圆曲线,拆解 simple_gmsm 源码中的点乘实现,分析国密方案在安全性与工程生态上的优劣。
【密码学百科】威胁模型与安全目标:CIA 三要素之外
密码学的安全性不是一个模糊的概念——它需要精确的定义、明确的攻击者模型和可验证的安全目标。本文从 CIA 三要素出发,深入 Dolev-Yao 模型、前向保密等现代安全概念
【密码学百科】Diffie-Hellman 密钥交换与离散对数问题
1976 年的 Diffie-Hellman 论文开启了公钥密码学革命——本文深入 DH 协议的数学基础,分析 CDH/DDH 假设的层次关系,剖析小子群攻击和 Logjam 等实际威胁