03

KDF · Symmetric Ratchet 机制

单链派生 · HMAC-SHA256

ChainKey 不断推进,每步派生一个用完即丢的 MessageKey · CK / MK 分离原因

🔁 单条 Symmetric Ratchet — 输入: CK_n · 输出: (CK_{n+1}, MK_n) — 每发一条消息推进一格 📌 常量 C 公开常数(实现中常用 0x01 派生 MK / 0x02 派生下一个 CK)— 双方约定相同 CK₀ = 初始 RootKey KDF HMAC SHA-256 CK₁ 留在状态 KDF SHA-256 CK₂ 留在状态 KDF SHA-256 CK₃ 留在状态 KDF SHA-256 CK₄ MK 派生: MK₁ 用完即丢 MK₂ 用完即丢 MK₃ 用完即丢 MK₄ 用完即丢 ↓ encrypt(M₁) ↓ encrypt(M₂) ↓ encrypt(M₃) ↓ encrypt(M₄) 🔍 KDF 内部展开 · 单个节点放大 CK_in C (常量) HMAC-SHA256 → 64 字节输出 key data 64 bytes prefix(32) suffix(32) CK_out MK 代码示意: let kdf = hmacSHA256(key: ckIn, data: c) return (ckOut: kdf.prefix(32), mk: kdf.suffix(32)) ❓ 为什么 ChainKey 和 MessageKey 必须分离? ❌ 反例: 假如直接用 CK_n 加密消息 • CK_n 留在状态用于推进下一格 • 攻击者拿到 CK_n → 可解 M_n 也可推 CK_{n+1} → 解 M_{n+1}… → 失去前向安全 ✅ 正确做法: CK 留下推进, MK 用完即丢 • MK_n 加密完 M_n 立即从内存清除 • 攻击者拿到 CK_n 也只能推未来, 反推不了过去 💡 带走结论 单条对称棘轮 = 单向 hash 链 + 每步分叉出消息密钥 ChainKey 是"棘轮的齿"(保留在状态、推进下一格)· MessageKey 是"齿上掉下的钥匙"(一次性、用完即丢)
CK

ChainKey · 保留型

  • • 长度:32 字节
  • • 存活:留在 Session 状态中(直到下次推进)
  • • 推进:HMAC(CK_n, C) → CK_{n+1}
  • • 不直接参与加密,仅作为派生源
MK

MessageKey · 一次性

  • • 长度:32 字节(或派生出 enc + auth + iv)
  • • 存活:用完即从内存清除(zeroize)
  • • 用途:AES-256-GCM 加密一条消息
  • • 索引:用 chain_index 标识第几个
KDF

KDF · 单向哈希

  • • 算法:HMAC-SHA256
  • • 单向性:从输出无法反推输入(密码学假设)
  • • 公开常量 C:让同一个 CK 能派生出两种用途
  • • Olm 实践:用 0x01/0x02 两个常数分别派生 MK/CK
💡 一句话理解: Symmetric Ratchet 就像一根单向齿轮 — ChainKey 是齿轮的"齿"(每转一格留下一个新齿、旧齿不可恢复), MessageKey 是齿上飞出的"火花"(瞬间用一次就消失)。 这种"留下推进 + 飞出即丢"的分工,是单条棘轮实现前向安全的根本机制。

📚 基础知识速查 · Reference

不熟悉以下底层概念? 这里是 30 秒回顾。

算法HMAC-SHA256 输出长度

固定 32 字节 (256 bits)。可被分割为 prefix(16) + suffix(16) 或其他组合, 派生多个独立的 32B 子密钥。

HMAC(key, data) → 32 bytes

设计Domain Separation

用不同的常量 (如 0x01 / 0x02) 作为 KDF 输入, 确保不同用途的派生 key 互相独立、互不影响。

MK = HMAC(CK, 0x01)
CK' = HMAC(CK, 0x02)

数据结构Hash Chain (哈希链)

形如 H(H(H(seed))) 的单向序列, 每一步都不可逆。Symmetric Ratchet 的本质就是带分叉的 hash chain。

x₀ → H(x₀) → H(H(x₀)) → …

算法AEAD (认证加密)

加密 + 认证一体化的算法, 如 AES-GCM。Signal 用 MK 派生 enc/auth/iv 三件套来调用 AEAD。

(plaintext, AD) → ciphertext + tag