08

X3DH → Double Ratchet 衔接

Bridge · 接力

SharedSec 如何成为 RootKey · EK_A 如何成为首条消息的 ratchet_key · 双方对称启动

🪢 接力机制 — X3DH 算出的 SharedSec 作为 Double Ratchet 的初始 RootKey · EK_A 作为首条 ratchet_key ① X3DH 终点 (产出 SharedSec) DH₁ = DH(IK_A, SPK_B) 混身份+中期 DH₂ = DH(EK_A, IK_B) 混临时+长期 DH₃ = DH(EK_A, SPK_B) 前向安全 DH₄ = DH(EK_A, OTK_B) 单次隔离 KDF HKDF SHA-256 Shared Sec 详细参见 #07 输出: 一段 32 字节高熵密钥 Alice 与 Bob 各自独立算出, 必相等 → 接力 → SharedSec ≜ RootKey₁ 无任何变换, 直接复用 ② Double Ratchet 起点 (RootKey 注入) RootKey₁ (= SharedSec) EK_A^priv (X3DH 时已生成) SPK_B^pub (从 Bob's bundle) KDF_RK DH+HMAC SHA-256 RK₂ 下一轮 SendChain₁ CK₁ → Alice 已可发首条! 不需等 Bob 回 SK₁ 派生自 CK₁ 🪞 双方对称启动 (Alice 与 Bob 都进入相同 RootKey₁ 状态) Alice 视角 ① X3DH 计算 → SharedSec (用自己 priv + Bob 的 pub) ② RootKey₁ ← SharedSec ③ 用 EK_A^priv + SPK_B^pub 做 DH → 得 Sec₁ ④ KDF(RootKey₁, Sec₁) → (RootKey₂, ChainKey₁) ⑤ ChainKey₁ → SK₁ → encrypt(M₁) 📤 发出: { ratchet_key=EK_A^pub, ciphertext, otk_id } Bob 视角 ① 收到 msg, 取出 EK_A^pub, IK_A^pub, otk_id ② X3DH 计算 → 同样的 SharedSec ✓ ③ RootKey₁ ← SharedSec ④ 用 SPK_B^priv + EK_A^pub 做 DH → 得 Sec₁ ✓ (DH 对称) ⑤ KDF(RootKey₁, Sec₁) → 同 (RootKey₂, ChainKey₁) 📥 用 ChainKey₁ 派生 SK₁ → decrypt(ciphertext) ✓ 🔑 关键洞察 — 为什么首条消息的 ratchet_key 是 EK_A? 复用而非新建: EK_A 在 X3DH 中已生成且公钥已发出 — Alice 不需要再生成新的 ratchet 密钥对 无缝衔接: EK_A 既参与 X3DH 的最后 3 个 DH, 又作为 Double Ratchet 的初始 ratchet_key — 一个密钥两种用途 发送端零延迟: Alice 还没等 Bob 回复就能加密 — 因为她已经有 RootKey + EK_A^priv + SPK_B^pub 三件套 💡 X3DH 的产物 (SharedSec + EK_A) 即 Double Ratchet 的初始燃料 (RootKey + 首条 ratchet_key) — 接力, 不是重启
桥接

SharedSec → RootKey₁

  • • 无变换直接赋值(实现里也只是改个字段名)
  • • 双方各自算出, 数学保证相等
  • • 之后 RootKey 由 Double Ratchet 接管, X3DH 退出
  • • X3DH 的全部"复杂性"都浓缩进 SharedSec 这一段
复用

EK_A 的双重身份

  • • X3DH 阶段: 临时密钥, 参与 3/4 个 DH (DH₂, DH₃, DH₄)
  • • Double Ratchet 阶段: 第一条 ratchet_key
  • • 同一个密钥对, 两种用途, 节省一次密钥生成
  • • Bob 收到首条时一并完成 X3DH 验证 + DR 初始化
先发

Alice 零延迟启动

  • • Alice 端: 拉到 Bob bundle 后即可计算所有内容
  • • 不需等 Bob "握手响应"
  • • 离线消息可达 (Bob 上线后才解密)
  • • Bob 上线后处理: X3DH 还原 + DR 解密 — 一气呵成
💡 一句话理解: X3DH 是"点火 + 接力"的混合体——点火(4 个 DH 算出 SharedSec)和接力(SharedSec→RootKey₁、EK_A→ratchet_key)发生在同一时刻。 Alice 算完 X3DH 立刻就能发出第一条加密消息, 而 Bob 收到后处理 X3DH + Double Ratchet 也是同一动作。 这就是为什么 Signal 能做到"零握手延迟 + 完整安全属性"。

📚 基础知识速查 · Reference

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

算法HKDF (HMAC-based KDF)

标准化 KDF (RFC 5869), 两步走: extract(salt, IKM) → PRK, expand(PRK, info, L) → OKM。

SharedSec = HKDF(DH₁‖DH₂‖DH₃‖DH₄)

设计Protocol Composition

不同协议子模块通过明确的输入/输出契约组合在一起。X3DH 的产物无变换地传给 DR — 这是可证明安全性的关键。

X3DH.SharedSec → DR.RootKey₁

概念零延迟启动 (0-RTT)

发送方不需等接收方响应就能发出第一条加密消息。Signal 通过 X3DH 的"预共享密钥包"实现这一点。

vs TLS 1.3 1-RTT / 0-RTT

协议密钥复用风险

EK 同时参与 X3DH 和 DR 的 ratchet_key — 两阶段共用一个密钥需要协议设计层面证明无安全降级。

EK 在 X3DH 后被"重新解释"为 ratchet