CH1

为什么要加密

Step 1 / 25
A Alice B Bob "Hi Bob! 转账 1000" E Eve · 偷听者 "Hi Bob! 转账 1000" 👀 🔑 K 🔑 K 密文(用 K 加密) x9F2cA7e1b3... ???? 看不懂 网络上传的内容,谁都能监听 双方共享一把密钥 K,加密后 Eve 看到的是乱码 A Alice B Bob 🔑 K ❓ 怎么把 K 传给 Bob? 直接发,Eve 也会截到 Alice 的密钥对 🔒 私钥 a (本地) 📢 公钥 A = g^a (可公开) Bob 的密钥对 🔒 私钥 b (本地) 📢 公钥 B = g^b (可公开) Alice 收到 B Bob 收到 A ECDH · 椭圆曲线 Diffie-Hellman Alice: a × B = ab Bob:  b × A = ab 双方算出同一个 ab ✓ Eve 只看到 A 和 B,算不出 (在椭圆曲线上 × 是标量乘法,但你可以先当成普通乘法理解) 来自 ECDH 的共享秘密 ab KDF 🔑 对称密钥 K 用于 AES-GCM / ChaCha20-Poly1305 用 K 加密第 1 条消息 M₁ Enc(K, M₁) = c₁ ⚠️ 但还有问题 如果 Alice 和 Bob 每次都用同一对 (a, A) (b, B),那 ab 永远不变 私钥某天泄漏 → 攻击者算出 ab → 历史所有密文一夜沦陷 下一章引入 KDF 链解决"每条消息都用不同密钥"的问题 问题:钥匙怎么传给对方? 现代密码学:每人一对密钥,公钥发出去,私钥永不出门 双方在网络上只交换公钥,却算出同一个秘密 用 ECDH 出来的秘密当密钥加密 —— 但还有问题 🔑 K 同一把 💣 M₁ 密文 c₁ M₂ 密文 c₂ M₃ 密文 c₃ 💀 K 一泄漏 → M₁ M₂ M₃ 全暴露 想要:每条消息一把不同的密钥;并且即使现在的密钥泄漏,过去的消息仍安全。 输入 CK KDF 密钥派生函数 通常是 HMAC-SHA256 单向 · 不可逆 输出无法反推输入 新 CK · 给下一轮用 MK · 这条消息的密钥 把『链密钥 CK』喂进 KDF,吐出『下一个 CK』和『这条消息的 MK』。 CK 用于继续往下推;MK 用一次就丢。 CK₁ CK₂ CK₃ CK₄ KDF KDF KDF KDF MK₁ 加密 M₁ MK₂ 加密 M₂ MK₃ 加密 M₃ MK₄ 加密 M₄ Symmetric Ratchet · 对称棘轮 每发一条消息推进一格,每条消息用全新的 MK CK 一直往下推(绿色);每格岔出一个一次性的 MK(粉色)用完即丢 攻击者在此偷到 CK₃ 💀 CK₃ ⚠️ MK₁ ✓ 安全 MK₂ ✓ 安全 ✅ 前向保密 (Forward Secrecy) 由于 KDF 不可逆,攻击者无法从 CK₃ 倒推回 CK₁、CK₂,自然也算不出 MK₁、MK₂ "现在被入侵,过去仍安全" —— FS 是 Signal 的核心安全保证之一 用同一把 K 加密 N 条消息:一旦泄漏,全部暴露 KDF 是一个『单向搅拌机』:能往前推,不能往回算 把 KDF 串成链,每条消息有专属的 MK 即使现在的 CK 被偷,过去的 MK 仍安全 —— 这就是『前向保密 FS』 攻击者偷到 CK₃ 💀 CK₁ ✓ CK₂ ✓ CK₃ ⚠️ CK₄ ✗ CK₅ ✗ ❌ 缺少『后向恢复 PCS』 KDF 链是确定的:偷到 CK₃ 后,用 KDF 一直算下去就能得到所有未来的 CK 攻击者一次入侵后,未来所有消息也持续被破。需要新办法。 CK_old 🎲 新的 DH 引入新的随机性 root key 更新 RK = KDF(RK, DH) 新 CK ✨ 全新出发 每隔一段时间做一次新 DH,注入新的随机性,让 root key 重新派生 攻击者即使偷到旧 CK,也无法预测注入的新 DH 输出 ✅ 几次新 DH 后,攻击者再也跟不上 —— 这就是 PCS Alice Bob M₁ + Alice 新公钥 A₁ M₂ + Bob 新公钥 B₁(基于 A₁ 派生 RK) M₃ + Alice 新公钥 A₂(基于 B₁ 派生 RK) …如此交替进行(ping-pong) 每次发消息时附上自己的新公钥;收到对方新公钥后再派生新 RK 和新 CK。 不断重新协商,是『DH ratchet · 棘轮』里『棘』的来源 —— 单向不可回滚。 DH 输出 新一轮交换的结果 当前 RK root key(主时间线) KDF 把新 DH 和旧 RK 搅在一起 新 RK 下次 DH 时再喂进去 新 CK 送进 KDF 链开始派生 MK(回到 Ch3) RK 是主时间线,永远只往前走。每次新 DH 派生出新 RK 和一条全新的 CK 链。 root chain RK₁RK₂RK₃ RK₄RK₅RK₆ sending chain (Alice 发消息时推进) receiving chain (收到对方消息时推进) 三条链同时进行:RK 主线每次 DH 推进一格,并派生出一条新的 sending 或 receiving 链; 每条 CK 链上每发/收一条消息推进一格(symmetric ratchet)。这就是『Double Ratchet · 双棘轮』。 KDF 链是确定的:偷到一格,未来全暴露 解法:每隔一段注入一次新 DH,让 root key 重新派生 每条消息都带新公钥,双方交替更新(ping-pong) DH 输出 + 当前 RK → KDF → 新 RK + 新 CK 完整 Double Ratchet:root + sending + receiving 三条链同时演化 Bob (离线) B Server 📦 公钥仓库 Alice A B Bob 📴 离线(睡觉中) A Alice 想现在发消息 ❓ Bob 不在线,怎么做 DH? 之前所有 DH 都假设双方同时在线交换公钥。 现实里 Bob 可能离线几天 —— Alice 不能等。 IK_B · 身份密钥(长期) SPK_B · 签名预共享(中期) 上传(提前) 📦 IK_B 📦 SPK_B(带 Bob 签名) Bob 提前把『身份密钥 IK』和『签名预共享密钥 SPK』传到服务器。 IK 几乎永久不变(代表身份);SPK 定期轮换(一般一周/月)并由 IK 签名。 IK_B SPK_B OPK 池(一次性) OPK₁ OPK₂ OPK₃ OPK₄ OPK₅ 批量上传 📦 IK_B 📦 SPK_B 📦 OPK 池 OPK₁OPK₂OPK₃ OPK₄OPK₅ Bob 还会预生成一池『一次性密钥 OPK』批量上传。 每个 OPK 只用一次,用完即弃。池子快空了 Bob 再补充。 📦 IK_B 📦 SPK_B 📦 OPK₂(即将原子领走) OPK₁ OPK₃ OPK₄ … "取 Bob 的密钥包" 返回 (IK_B, SPK_B, OPK₂) Alice 收到 Bob 的 IK_B SPK_B(验证签名) OPK₂(只此一份) +自己有 IK_A、EK_A Alice 一次性取走 Bob 的『三件套』。服务器原子地把 OPK₂ 从池里删掉, 下次别人来要 Bob 的包,会拿到 OPK₁ 或 OPK₃,永远不会重复。 Alice 拥有 自己: IK_A, EK_A (一次性短暂) Bob: IK_B, SPK_B, OPK_B → 共 5 把公钥参与运算 DH₁ = IK_A × SPK_B 绑定 Alice 身份 ↔ Bob 中期密钥 DH₂ = EK_A × IK_B 绑定 Alice 临时密钥 ↔ Bob 身份 DH₃ = EK_A × SPK_B 提供新鲜性(每次新 EK) DH₄ = EK_A × OPK_B 提供后向恢复(一次性 OPK) 为什么要 4 个 DH?让攻击者必须同时拿到 Alice 的 IK 和 EK,以及 Bob 的 IK/SPK/OPK 才能破。 任何一个密钥泄漏都不足以打破整个 X3DH —— 这叫"密钥分摊安全"。 DH₁ DH₂ DH₃ DH₄ ‖ 拼接 KDF HMAC-based SharedSecret X3DH 的产出 32 bytes 随机数 Double Ratchet RK₀ · 初始 root key 交给 Ch4 接着跑 4 个 DH 拼接喂进 KDF,得到 SharedSecret —— 这就是 Double Ratchet 的初始 root key。 从这一刻起,Alice 和 Bob 进入 Ch4 描述的 ping-pong,每条消息都有专属 MK。 挑战:Bob 离线,Alice 想现在就发,怎么协商密钥? Bob 提前把 IK + SPK 上传到 Server 再批量上传一池 OPK(一次性,用完即弃) Alice 一次性取走『三件套』,Server 原子删除 OPK Alice 用自己的 (IK_A, EK_A) 和 Bob 的三件套做 4 个 DH 4 个 DH 输出 → KDF → SharedSecret → 接进 Double Ratchet 当 RK₀ Bob Server Alice ① 上传 IK + SPK + OPK 池 ② 请求 Bob 的密钥包 ③ 返回 (IK_B, SPK_B, OPK_B) · OPK 出队 ④ X3DH → SharedSec → RK₀ ⑤ 第 1 条消息 + Alice 公钥(密文) ⑥ 暂存,等 Bob 上线后转发 📱 Bob 上线 · 解密第 1 条 ⑦ 回复 + Bob 新公钥(Double Ratchet ping-pong 开始) …此后每条消息都附带新公钥,root chain 持续推进,每条消息有专属 MK 数据 / 密钥 Alice 看到 Server 看到 Bob 看到 IK / SPK / OPK 公钥 ✓ (自有) 私钥 IK_A / EK_A / IK_B… 部分 (自己的) 部分 (自己的) SharedSecret · 4 DH 输出 RK / CK / MK 每条消息的明文 每条消息的密文 ✓ (中转) 谁在跟谁通信(元数据) ✓ (能看到) Server 能转发密文、保存公钥包、看到通信元数据;但永远看不到明文和任何会话密钥。 这是 E2E (端到端加密) 的边界。元数据隐藏需要其他协议(如 Sealed Sender)解决。 时间 💀 攻击者偷到密钥 FS 区域 · 过去仍安全 短暂受损 PCS 区域 · 几次新 DH 后重新安全 KDF 单向性保证:偷到现在算不回过去 未做新 DH 之前 每次新 DH 注入新随机性,把攻击者甩开 前向保密 Forward Secrecy "现在被入侵,过去仍安全" 由 KDF 单向链保证(Ch3 step 10) 后向恢复 Post-Compromise Security "现在被入侵,未来还能恢复安全" 由 DH ratchet 注入新随机性保证(Ch4 step 12) 🎉 关键密钥速查 IK · 身份密钥 长期 · 几乎不变 代表你这个人;用于 X3DH 绑定身份 SPK · 签名预共享 中期 · 周/月轮换 由 IK 签名;防止被攻击者替换 OPK · 一次性密钥 短期 · 用一次 提供 X3DH 的后向恢复保证 EK · 短暂密钥 一次性 · Alice 发起方 每次开新会话时新生成;提供新鲜性 RK · root key 每次 DH 推进一格 主时间线;从 X3DH 的 SharedSec 启动 CK · chain key 每条消息推进一格 sending / receiving 各一条 MK · message key 每条消息一把 真正用来 AEAD(认证加密)这条消息 DH-EK · 棘轮短暂密钥 每条/每几条消息换 附在消息头里;驱动 DH ratchet ping-pong 🎉 恭喜走完 25 步! 你已经理解了 Signal 协议的全部核心:对称加密 · ECDH · KDF · Symmetric ratchet · DH ratchet · X3DH 想要回顾任何一步?点左侧章节的圆点直接跳转。 想看更深入的细节图谱?回到 ../advanced/index.html 探索老手版 12 张图。 把所有步骤串起来:注册 → 取走 → X3DH → 首消息 → ping-pong 谁能看到什么:E2E 加密的边界一目了然 两个安全方向:FS (左侧绿区) + PCS (右侧绿区)