3. ML-KEM算法规范精读

ML-KEM(FIPS 203)把整个密钥封装机制拆成三个确定性算法:KeyGen、Encaps、Decaps。三者共享同一套底层结构——参数集(k、n=256、q=3329、η₁、η₂、dᵤ、dᵥ)、NTT 域上的多项式运算、以及基于 SHAKE 的 XOF/PRF。理解规范的关键,不是记住每个函数名,而是看清“哪些量是输入、哪些量是内部状态、哪些量最终被编码进密文或密钥”。

一、KeyGen:从随机种子到 (ek, dk)

KeyGen 的输入是一个 32 字节随机种子 d(以及可选的 z,用于隐式拒绝)。规范内部先做种子扩展:(ρ, σ) ← G(d ‖ k),其中 G 是 SHA3-512,k 是参数集标识。ρ 是 32 字节的矩阵种子,σ 是 32 字节的噪声种子。

矩阵生成由 SampleNTT 完成:对 A 的每个元素 A[i][j],取 XOF(ρ, j, i) 输出的字节流,按 12 位一组解析,若值小于 q 则接受,否则丢弃并继续取字节,直到填满 256 个系数。注意索引顺序是 (j, i) 而非 (i, j),这是规范中容易写错的一处。

错误向量采样由 SamplePolyCBD_η 完成:从 PRF(σ, N) 取 64η 字节,按 η 位一组拆成 2η 个系数,两两相减得到中心二项分布采样。η₁ 用于 s 和 e,η₂ 用于 e₁、e₂。随后 s、e 进入 NTT 域,计算 t̂ = Â ∘ ŝ + ê。

输出编码:ek = ByteEncode₁₂(t̂) ‖ ρ,dk = ByteEncode₁₂(ŝ) ‖ ek ‖ H(ek) ‖ z。其中 H 是 SHA3-256,z 用于 Decaps 的隐式拒绝分支。

二、Encaps:从 ek 与随机消息到 (K, c)

Encaps 输入是 ek 和 32 字节随机消息 m。先计算 (K, r) ← G(m ‖ H(ek)),K 是 32 字节共享密钥,r 是 32 字节加密随机性。随后用 SamplePolyCBD_η₁ 从 r 采样 y,用 SamplePolyCBD_η₂ 从 r 采样 e₁、e₂。

核心计算是 u = NTT⁻¹(Âᵀ ∘ ŷ) + e₁,v = NTT⁻¹(t̂ᵀ ∘ ŷ) + e₂ + Decompress₁(m)。这里 Âᵀ 表示转置,m 先经 Decompress₁ 映射到多项式系数,再叠加到 v 上。最后压缩:c₁ = Compress_dᵤ(u),c₂ = Compress_dᵥ(v),密文 c = c₁ ‖ c₂。

三、Decaps:从 dk 与 c 恢复 K

Decaps 输入是 dk 和密文 c。先从 dk 中拆出 ŝ、ek、H(ek)、z。对 c 解压得到 u′、v′,计算 m′ = Compress₁(Decode₁(v′ − NTT⁻¹(ŝᵀ ∘ NTT(u′))))。然后重加密:(K′, r′) ← G(m′ ‖ H(ek)),用 r′ 重新生成密文 c′。若 c = c′,输出 K′;否则输出 J(z ‖ c),即隐式拒绝。

注意:Decaps 的隐式拒绝不返回错误,而是返回一个伪随机密钥。调用方无法从返回值判断是否被篡改,必须依赖上层协议(如 TLS 的 Finished 消息)来发现不匹配。

四、参数与编码速查

参数ML-KEM-512ML-KEM-768ML-KEM-1024
k234
η₁ / η₂3 / 22 / 22 / 2
dᵤ / dᵥ10 / 410 / 411 / 5
ek 字节80011841568
dk 字节163224003168
c 字节76810881568

编码规则:ByteEncode_d 把多项式系数按 d 位打包,ByteDecode_d 反向还原;Compress_d 做 round(2^d / q · x) mod 2^d,Decompress_d 做 round(q / 2^d · y)。所有舍入都采用“四舍五入到最近整数,平局取偶”的规则,实现时必须与规范一致,否则 Decaps 会因 c′ ≠ c 而误触发拒绝。

KeyGen 种子 d (32B) 种子 z (32B) G(d ‖ k) → (ρ, σ) SampleNTT(ρ) SampleCBD(σ) t̂ = Â ∘ ŝ + ê Encaps ek 消息 m (32B) G(m ‖ H(ek)) → (K, r) SampleCBD(r) → y, e₁, e₂ u = Âᵀ∘ŷ + e₁, v = t̂ᵀ∘ŷ + e₂ + m Decaps dk 密文 c 解压 u′, v′;m′ = Decode(v′ − ŝᵀu′) 重加密 c′ = Encaps(ek, m′) c = c′ ? K′ : J(z ‖ c) 共享底层原语 XOF / PRF SHAKE128 / SHAKE256 G = SHA3-512, H = SHA3-256 NTT / NTT⁻¹ q = 3329, n = 256 ζ = 17, 7 层蝶形 采样 SampleNTT: 12-bit 拒绝 SampleCBD: 中心二项分布 编码 / 压缩 ByteEncode_d / Decode_d Compress_d / Decompress_d 隐式拒绝 J(z ‖ c) 不返回错误 ek c ek, dk K, c K 或 J(z‖c) 密文编码与压缩路径 u (k 个多项式) Compress_dᵤ ByteEncode_dᵤ c₁ (dᵤ·k·n/8 B) v 同理经 Compress_dᵥ 与 ByteEncode_dᵥ 得到 c₂;c = c₁ ‖ c₂

把三个算法放在一起看,会发现它们共享同一套“种子→采样→NTT→编码”的流水线,区别只在于输入种子来源和最终输出形态。KeyGen 用 d 生成长期密钥,Encaps 用 m 生成一次性密钥和密文,Decaps 则用 dk 中的 ŝ 做一次“解密+重加密”的一致性校验。真正需要逐字节对齐规范的,是 SampleNTT 的拒绝采样边界、SampleCBD 的位拆分顺序、以及 Compress 的舍入规则——这三处一旦偏差,Decaps 的 c′ 就会与 c 不等,导致合法密文被误判为篡改。