注意
各种 router 实现中的实施、测试和部署正在进行中。请查看这些实现的文档以了解状态。
概述
这是 ECIES-X25519-AEAD-Ratchet 协议 ECIES 的 PQ 混合变体。它是整体 PQ 提案 Prop169 中第一个被批准的阶段。有关总体目标、威胁模型、分析、替代方案和其他信息,请参见该提案。
本规范仅包含与标准 ECIES 的差异,必须与该规范一起阅读。
设计
我们使用 NIST FIPS 203 标准 FIPS203 ,该标准基于但不兼容 CRYSTALS-Kyber(版本 3.1、3 及更早版本)。
混合握手按照 Noise-Hybrid 中的规范执行。
密钥交换
我们为 Ratchet 定义了一种混合密钥交换。PQ KEM 仅提供临时密钥,不直接支持静态密钥握手,如 Noise IK。
我们按照 FIPS203 定义三种 ML-KEM 变体,总共增加了 3 种新的加密类型。混合类型仅与 X25519 结合定义。
新的加密类型包括:
| Type | Code |
|---|---|
| MLKEM512_X25519 | 5 |
| MLKEM768_X25519 | 6 |
| MLKEM1024_X25519 | 7 |
需要新的加密技术
- ML-KEM (原名 CRYSTALS-Kyber) FIPS203
- SHA3-128 (原名 Keccak-256) FIPS202 仅用于 SHAKE128
- SHA3-256 (原名 Keccak-512) FIPS202
- SHAKE128 和 SHAKE256 (SHA3-128 和 SHA3-256 的 XOF 扩展) FIPS202
SHA3-256、SHAKE128 和 SHAKE256 的测试向量可在 NIST-VECTORS 获取。
注意 Java bouncycastle 库支持上述所有算法。C++ 库支持在 OpenSSL 3.5 OPENSSL 中提供。
规范
通用结构
请参阅通用结构规范 COMMON 了解密钥长度和标识符。
握手模式
握手使用Noise 握手模式。
使用以下字母映射:
- e = 一次性临时密钥
- s = 静态密钥
- p = 消息载荷
- e1 = 一次性临时PQ密钥,从Alice发送到Bob
- ekem1 = KEM密文,从Bob发送到Alice
以下对 XK 和 IK 的混合前向保密 (hfs) 修改是按照 Noise-Hybrid 第 5 节中的规定进行的:
IK: IKhfs:
<- s <- s
... ...
-> e, es, s, ss, p -> e, es, e1, s, ss, p
<- tag, e, ee, se, p <- tag, e, ee, ekem1, se, p
<- p <- p
p -> p ->
e1 and ekem1 are encrypted. See pattern definitions below.
NOTE: e1 and ekem1 are different sizes (unlike X25519)
e1 模式定义如下,如 Noise-Hybrid 第 4 节所述:
For Alice:
(encap_key, decap_key) = PQ_KEYGEN()
// EncryptAndHash(encap_key)
ciphertext = ENCRYPT(k, n, encap_key, ad)
n++
MixHash(ciphertext)
For Bob:
// DecryptAndHash(ciphertext)
encap_key = DECRYPT(k, n, ciphertext, ad)
n++
MixHash(ciphertext)
ekem1模式定义如下,如Noise-Hybrid 第4节所述:
For Bob:
(kem_ciphertext, kem_shared_key) = ENCAPS(encap_key)
// EncryptAndHash(kem_ciphertext)
ciphertext = ENCRYPT(k, n, kem_ciphertext, ad)
MixHash(ciphertext)
// MixKey
MixKey(kem_shared_key)
For Alice:
// DecryptAndHash(ciphertext)
kem_ciphertext = DECRYPT(k, n, ciphertext, ad)
MixHash(ciphertext)
// MixKey
kem_shared_key = DECAPS(kem_ciphertext, decap_key)
MixKey(kem_shared_key)
已定义的 ML-KEM 操作
我们定义以下函数,对应于 FIPS203 中定义的加密构建块。
(encap_key, decap_key) = PQ_KEYGEN()
Alice 创建封装密钥和解封装密钥。封装密钥在 NS 消息中发送。encap_key 和 decap_key 的大小根据 ML-KEM 变体而变化。
(ciphertext, kem_shared_key) = ENCAPS(encap_key)
Bob 使用在 NS 消息中接收到的密文计算密文和共享密钥。密文在 NSR 消息中发送。密文大小根据 ML-KEM 变体而变化。kem_shared_key 始终为 32 字节。
kem_shared_key = DECAPS(ciphertext, decap_key)
Alice 使用在 NSR 消息中收到的密文计算共享密钥。kem_shared_key 始终为 32 字节。
请注意,encap_key 和 ciphertext 都在 Noise 握手消息 1 和 2 中的 ChaCha/Poly 块内被加密。它们将作为握手过程的一部分被解密。
kem_shared_key 通过 MixHash() 混合到链式密钥中。详见下文。
Noise 握手 KDF
概述
混合握手在 Noise-Hybrid 中定义。第一条消息从 Alice 发送到 Bob,在消息载荷之前包含 e1(封装密钥)。这被视为一个额外的静态密钥;对其调用 EncryptAndHash()(作为 Alice)或 DecryptAndHash()(作为 Bob)。然后照常处理消息载荷。
第二条消息,从 Bob 发送给 Alice,在消息载荷之前包含 ekem1,即密文。这被视为一个额外的静态密钥;对其调用 EncryptAndHash()(作为 Bob)或 DecryptAndHash()(作为 Alice)。然后,计算 kem_shared_key 并调用 MixKey(kem_shared_key)。接着按常规处理消息载荷。
Noise 标识符
这些是 Noise 初始化字符串:
- “Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256”
- “Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256”
- “Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256”
NS消息的Alice KDF
在 ’es’ 消息模式之后和 ’s’ 消息模式之前,添加:
This is the "e1" message pattern:
(encap_key, decap_key) = PQ_KEYGEN()
// EncryptAndHash(encap_key)
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
ciphertext = ENCRYPT(k, n, encap_key, ad)
n++
// MixHash(ciphertext)
h = SHA256(h || ciphertext)
End of "e1" message pattern.
NOTE: For the next section (payload for XK or static key for IK),
the keydata and chain key remain the same, and n now equals 1
(instead of 0 for non-hybrid).
NS 消息的 Bob KDF
在 ’es’ 消息模式之后和 ’s’ 消息模式之前,添加:
This is the "e1" message pattern:
// DecryptAndHash(encap_key_section)
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
encap_key = DECRYPT(k, n, encap_key_section, ad)
n++
// MixHash(encap_key_section)
h = SHA256(h || encap_key_section)
End of "e1" message pattern.
NOTE: For the next section (payload for XK or static key for IK),
the keydata and chain key remain the same, and n now equals 1
(instead of 0 for non-hybrid).
NSR 消息的 Bob KDF
在 ’ee’ 消息模式之后和 ‘se’ 消息模式之前,添加:
This is the "ekem1" message pattern:
(kem_ciphertext, kem_shared_key) = ENCAPS(encap_key)
// EncryptAndHash(kem_ciphertext)
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
ciphertext = ENCRYPT(k, n, kem_ciphertext, ad)
// MixHash(ciphertext)
h = SHA256(h || ciphertext)
// MixKey(kem_shared_key)
keydata = HKDF(chainKey, kem_shared_key, "", 64)
chainKey = keydata[0:31]
End of "ekem1" message pattern.
Alice KDF for NSR 消息
在 ’ee’ 消息模式之后和 ‘ss’ 消息模式之前,添加:
This is the "ekem1" message pattern:
// DecryptAndHash(kem_ciphertext_section)
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
kem_ciphertext = DECRYPT(k, n, kem_ciphertext_section, ad)
// MixHash(kem_ciphertext_section)
h = SHA256(h || kem_ciphertext_section)
// MixKey(kem_shared_key)
kem_shared_key = DECAPS(kem_ciphertext, decap_key)
keydata = HKDF(chainKey, kem_shared_key, "", 64)
chainKey = keydata[0:31]
End of "ekem1" message pattern.
split() 的密钥派生函数
未更改
消息格式
NS 格式
变更:当前的 ratchet 在第一个 ChaCha 部分包含静态密钥,在第二部分包含载荷。使用 ML-KEM 后,现在有三个部分。第一部分包含加密的 PQ 公钥。第二部分包含静态密钥。第三部分包含载荷。
加密格式:
+----+----+----+----+----+----+----+----+
| |
+ New Session Ephemeral +
| Public Key |
+ 32 bytes +
| Encoded with Elligator2 |
+----+----+----+----+----+----+----+----+
| |
+ ML-KEM encap_key +
| ChaCha20 encrypted data |
+ (see table below for length) +
| |
~ ~
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for encap_key Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
| |
+ X25519 Static Key +
| ChaCha20 encrypted data |
+ 32 bytes +
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for Static Key Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
| |
+ Payload Section +
| ChaCha20 encrypted data |
~ ~
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for Payload Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
解密格式:
Payload Part 1:
+----+----+----+----+----+----+----+----+
| |
+ ML-KEM encap_key +
| |
+ (see table below for length) +
| |
~ ~
| |
+----+----+----+----+----+----+----+----+
Payload Part 2:
+----+----+----+----+----+----+----+----+
| |
+ X25519 Static Key +
| (32 bytes) |
+ +
| |
+----+----+----+----+----+----+----+----+
Payload Part 3:
+----+----+----+----+----+----+----+----+
| |
+ Payload Section +
| |
~ ~
| |
+ +
| |
+----+----+----+----+----+----+----+----+
大小:
| Type | Type Code | X len | NS len | NS Enc len | NS Dec len | PQ key len | pl len |
|---|---|---|---|---|---|---|---|
| X25519 | 4 | 32 | 96+pl | 64+pl | pl | -- | pl |
| MLKEM512_X25519 | 5 | 32 | 912+pl | 880+pl | 800+pl | 800 | pl |
| MLKEM768_X25519 | 6 | 32 | 1296+pl | 1360+pl | 1184+pl | 1184 | pl |
| MLKEM1024_X25519 | 7 | 32 | 1680+pl | 1648+pl | 1568+pl | 1568 | pl |
NSR 格式
变更:当前的 ratchet 在第一个 ChaCha 部分有一个空负载,而负载在第二部分。使用 ML-KEM 后,现在有三个部分。第一部分包含加密的 PQ 密文。第二部分有一个空负载。第三部分包含负载。
加密格式:
+----+----+----+----+----+----+----+----+
| Session Tag 8 bytes |
+----+----+----+----+----+----+----+----+
| |
+ Ephemeral Public Key +
| 32 bytes |
+ Encoded with Elligator2 +
| |
+----+----+----+----+----+----+----+----+
| |
+ ML-KEM ciphertext +
| ChaCha20 encrypted data |
+ (see table below for length) +
| |
~ ~
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for ciphertext Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for key Section (no data) +
| 16 bytes |
+----+----+----+----+----+----+----+----+
| |
+ Payload Section +
| ChaCha20 encrypted data |
~ ~
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) for Payload Section +
| 16 bytes |
+----+----+----+----+----+----+----+----+
解密格式:
Payload Part 1:
+----+----+----+----+----+----+----+----+
| |
+ ML-KEM ciphertext +
| |
+ (see table below for length) +
| |
~ ~
| |
+----+----+----+----+----+----+----+----+
Payload Part 2:
empty
Payload Part 3:
+----+----+----+----+----+----+----+----+
| |
+ Payload Section +
| |
~ ~
| |
+ +
| |
+----+----+----+----+----+----+----+----+
大小:
| Type | Type Code | Y len | NSR len | NSR Enc len | NSR Dec len | PQ CT len | opt len |
|---|---|---|---|---|---|---|---|
| X25519 | 4 | 32 | 72+pl | 32+pl | pl | -- | pl |
| MLKEM512_X25519 | 5 | 32 | 856+pl | 816+pl | 768+pl | 768 | pl |
| MLKEM768_X25519 | 6 | 32 | 1176+pl | 1136+pl | 1088+pl | 1088 | pl |
| MLKEM1024_X25519 | 7 | 32 | 1656+pl | 1616+pl | 1568+pl | 1568 | pl |
开销分析
密钥交换
大小增加(字节):
| Type | Pubkey (NS) | Ciphertext (NSR) |
|---|---|---|
| MLKEM512_X25519 | +816 | +784 |
| MLKEM768_X25519 | +1200 | +1104 |
| MLKEM1024_X25519 | +1584 | +1584 |
据CLOUDFLARE 报告的速度:
| Type | Relative speed |
|---|---|
| X25519 DH/keygen | baseline |
| MLKEM512 | 2.25x faster |
| MLKEM768 | 1.5x faster |
| MLKEM1024 | 1x (same) |
| XK | 4x DH (keygen + 3 DH) |
| MLKEM512_X25519 | 4x DH + 2x PQ (keygen + enc/dec) = 4.9x DH = 22% slower |
| MLKEM768_X25519 | 4x DH + 2x PQ (keygen + enc/dec) = 5.3x DH = 32% slower |
| MLKEM1024_X25519 | 4x DH + 2x PQ (keygen + enc/dec) = 6x DH = 50% slower |
NIST安全类别在NIST-PQ-END 第10页幻灯片中有总结。初步标准:我们的最低NIST安全类别对于混合协议应为2,对于纯PQ协议应为3。
| Category | As Secure As |
|---|---|
| 1 | AES128 |
| 2 | SHA256 |
| 3 | AES192 |
| 4 | SHA384 |
| 5 | AES256 |
这些都是混合协议。可能需要优先选择 MLKEM768;MLKEM512 的安全性不够。
NIST 安全类别 FIPS203 :
| Algorithm | Security Category |
|---|---|
| MLKEM512 | 1 |
| MLKEM768 | 3 |
| MLKEM1024 | 5 |
基于安全类别和密钥长度,建议用于初始支持的类型是:
MLKEM768_X25519 (类型 6)
实现说明
库支持
Bouncycastle、BoringSSL 和 WolfSSL 库现在支持 MLKEM。OpenSSL 支持将在 2025 年 4 月 8 日的 3.5 版本中提供 OPENSSL 。
共享 Tunnels
基于消息1(新会话消息)的长度检查,应该可以在同一个tunnel上自动分类/检测多种协议。以MLKEM512_X25519为例,消息1的长度比当前ratchet协议大816字节,最小消息1大小(仅包含DateTime载荷)为919字节。当前ratchet的大多数消息1载荷小于816字节,因此可以将其分类为非混合ratchet。大消息可能是POST请求,这种情况比较少见。
因此推荐的策略是:
- 如果消息 1 少于 919 字节,则是当前的 ratchet 协议。
- 如果消息 1 大于或等于 919 字节,则可能是 MLKEM512_X25519。首先尝试 MLKEM512_X25519,如果失败,则尝试当前的 ratchet 协议。
这应该允许我们在同一个目标上高效支持标准ratchet和混合ratchet,就像我们之前在同一个目标上支持ElGamal和ratchet一样。因此,我们可以比不能为同一个目标支持双协议的情况下更快地迁移到MLKEM混合协议,因为我们可以为现有目标添加MLKEM支持。
要求支持的组合包括:
- X25519 + MLKEM512
- X25519 + MLKEM768
- X25519 + MLKEM1024
以下组合可能比较复杂,不要求必须支持,但可能会支持,这取决于具体实现:
- 多个 MLKEM
- ElG + 一个或多个 MLKEM
- X25519 + 一个或多个 MLKEM
- ElG + X25519 + 一个或多个 MLKEM
不要求在同一个目标上支持多种 MLKEM 算法(例如,MLKEM512_X25519 和 MLKEM_768_X25519)。只需选择一种即可。这取决于具体实现。
不需要在同一个目标上支持三种算法(例如 X25519、MLKEM512_X25519 和 MLKEM769_X25519)。分类和重试策略可能过于复杂。配置和配置用户界面可能过于复杂。这取决于具体实现。
不需要在同一个目标上同时支持ElGamal和混合算法。ElGamal已经过时,而且只有ElGamal + hybrid(没有X25519)意义不大。另外,ElGamal和Hybrid New Session Messages都很大,所以分类策略通常需要尝试两种解密方式,这会很低效。具体实现依赖于实现方式。
客户端可以在同一tunnel上为X25519和混合协议使用相同或不同的X25519静态密钥,具体取决于实现。
前向保密性
ECIES 规范允许在新会话消息载荷中包含 Garlic Messages,这允许初始流数据包(通常是 HTTP GET)与客户端的 leaseset 一起进行 0-RTT 传输。然而,新会话消息载荷不具备前向保密性。由于本提案强调为棘轮增强前向保密性,实现可能或应该推迟包含流载荷或完整流消息,直到第一个现有会话消息。这将以牺牲 0-RTT 传输为代价。策略也可能取决于流量类型或 tunnel 类型,或者例如 GET 与 POST 的区别。具体取决于实现。
新会话大小
MLKEM 将显著增加新会话消息的大小,如上所述。这可能会大大降低新会话消息通过 tunnel 传输的可靠性,因为它们必须被分片成多个 1024 字节的 tunnel 消息。传输成功率与分片数量成指数反比关系。实现可以使用各种策略来限制消息大小,但代价是牺牲 0-RTT 传输。具体取决于实现。