09

OPK 防复用机制

Single-Use · 服务端契约

OPK 池生命周期 · 原子领取 · 池耗尽降级 · 客户端补充时机

🎯 关键契约: 每个 OTK 只能被领取并使用一次 — 服务端原子操作 + 客户端定期补充共同维护 Bob (OPK 提供者) Server (OPK 池管理) Senders (Alice / Charlie / …) ▶ Phase 1: Bob 注册时, 一次性生成 + 上传 OPK 池 本地生成 N 个 OPK 对 (otk¹, otk², …, otk¹⁰) 私钥本地保存 upload OPK 池: 10 个可用 1 2 3 4 5 6 7 8 9 10 ▶ Phase 2: Alice 与 Charlie 同时给 Bob 发消息 — 服务端必须保证两人各拿到不同的 OPK Alice (t=10ms) GET /bundle/bob 含 1 个 OPK Charlie (t=11ms) GET /bundle/bob 同时发起! ⚛ 原子操作 (transaction / 行锁) BEGIN; otk = SELECT 1 FROM opk WHERE bob FOR UPDATE DELETE FROM opk WHERE id = otk.id; COMMIT; otk¹ otk² (不同!) → 池剩余 8 个 ▶ Phase 3: 双方各自用领到的 OPK 完成 X3DH, 发起会话 Alice → Bob 用 otk¹ DH₄ = DH(EK_A, otk¹) Charlie → Bob 用 otk² DH₄ = DH(EK_C, otk²) 服务端不再持有 otk¹ otk² 的私钥引用 ▶ Phase 4: 持续被领取后, 池逐渐耗尽 ⚠ 池剩 0 个 SELECT 返回空 Dave (新发送方) GET /bundle/bob 收到 bundle 但无 OPK 字段 no otk 📉 降级方案 — Dave 端处理: X3DH 退化为 3DH (省略 DH₄), 仍然能完成密钥协商, 但失去 OPK 提供的"额外前向安全 + 防 replay" ▶ Phase 5: Bob 客户端检测池低位, 补充新 OPK 触发条件 (任一即可): • App 启动时检查 • 服务端推送"低位"告警 upload 50 new 池补充: + 50 → 总 50 个 … ×50 🛠️ 三大关键设计点 ① 原子领取 SELECT … FOR UPDATE + DELETE 同事务, 防止两个并发请求拿到同一个 OPK (race condition) ② 池耗尽降级 不能因为 OPK 耗尽就拒绝新会话, 必须返回不含 OTK 的 bundle, 客户端能识别并降级到 3DH ③ 客户端补充 Bob 定期 + 启动时检查池容量, 补充到目标值 (典型 100), 通过签名上传防止伪造 💡 OPK single-use = 服务端原子操作 (强制) + 客户端及时补充 (弹性) 共同维护的契约
服务端

原子领取 — 强一致

  • • 用 DB 行锁 (SELECT FOR UPDATE) 或 Redis 原子操作
  • • 领取与删除必须同事务, 不能"先返回再删"
  • • 出错时回滚, 防止 OPK 丢失或重复使用
  • • 也可记录使用日志, 用于审计
降级

池耗尽 — 3DH Fallback

  • • X3DH 缩水为 3DH(不计算 DH₄)
  • • 仍然完成会话, 但失去 OPK 提供的额外保障
  • • 会话密钥来自 IK + SPK (中期) + EK (临时)
  • • 产品上应给"高活跃用户"分配更大池子
客户端

补充策略

  • • 启动时: 查询服务端的池剩余数
  • • 周期性: 每天或每周拉取告警
  • • 阈值: 剩余低于 10 时立即补到 100
  • • 网络恢复后: 优先重传未完成的补充请求
攻击

常见威胁场景

  • • 池耗尽 DoS: 攻击者大量 fetch → 客户端补充慢
  • • 重放: 服务端必须删除已用 OPK, 否则可被重放
  • • 伪造: 上传必须 IK 签名, 防止他人冒充
  • • 应对: 限频 fetch + 快速补充 + 监控告警
💡 一句话理解: OPK 不是"加密用的钥匙", 而是"会话隔离的种子"—— 服务端必须像发"一次性入场券"那样把它发出去, 发完就撕。 池耗尽时降级到 3DH 是为可用性付出的代价, 客户端定期补货是消费方的责任。

📚 基础知识速查 · Reference

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

数据库ACID Transaction

原子性 (Atomicity)、一致性 (Consistency)、隔离性 (Isolation)、持久性 (Durability)。OPK 领取必须在事务里完成。

BEGIN; ... COMMIT;

SQLSELECT FOR UPDATE

行级悲观锁。防止并发请求拿到同一行。也可用 Redis SETNX、ZK 等价机制实现。

SELECT * FROM opk WHERE bob
FOR UPDATE SKIP LOCKED LIMIT 1;

设计模式Single-Use Token

一次性令牌模式。常用于 CSRF token、邮箱验证、OAuth 授权码、OPK 等场景。共同特征: 验证后立即作废。

CSRF · email-verify · auth_code · OPK

安全Replay Attack

攻击者重放旧合法消息触发动作 (如重复扣款)。OPK single-use 是 Signal 抗 replay 的关键之一 — 旧 OPK 不能再次握手。

defense: nonce + single-use + timestamp