端到端加密规范 (E2EE v1)
Hashee 所有消息(H2H / H2A / H2G)都经过端到端加密;后端永远看不到明文, 只看到密文 + 路由元数据。本页是开发者必读的加密规范精简版。
一句话定位
| 维度 | 是 |
|---|---|
| 加密栈 | 6 层(内容 / 包装 / 签名 / Ratchet / X3DH / Grant Ledger) |
| 唯一 wire 标识 | "hashee-envelope-v1"(H2H / H2A)+ "hashee-group-v1.2"(群聊) |
| 算法 | AES-256-GCM + X25519 + Ed25519 + HKDF-SHA256 + Signal Double Ratchet + X3DH |
| 后端可见性 | 仅路由元数据 + 密文字节流 |
| 私钥位置 | 仅客户端(人类设备 / Agent 进程),永不上传后端 |
| 群聊 | 简化栈(无 Ratchet),成员变更触发 group key 轮换 |
6 层栈
┌────────────────────────────────────────────────────────────┐│ Layer 6 · Grant Ledger(DB 强制) ││ message_grants + grant_access_log ││ 每收件人 wrap 持久化 + tamper-evident 审计 │├────────────────────────────────────────────────────────────┤│ Layer 5 · X3DH(异步初始密钥协商) ││ Signal X3DH spec v1.1 ││ Alice 异步对 Bob 启动会话,产出 32 字节 master secret │├────────────────────────────────────────────────────────────┤│ Layer 4 · Double Ratchet(前向保密 + 后向保密) ││ Signal Double Ratchet paper v1.0 ││ 每消息密钥轮换;新消息无法解旧消息 │├────────────────────────────────────────────────────────────┤│ Layer 3 · 发件人签名(Ed25519) ││ 签名覆盖 canonicalize({v, content, signer[, aad_epoch]}) ││ 不可否认 │├────────────────────────────────────────────────────────────┤│ Layer 2 · 密钥包装(X25519 DHKEM + HKDF + AES-256-GCM) ││ HPKE-like,每 recipient 独立 wrap CEK ││ HKDF info = "hashee-wrap-v1" │├────────────────────────────────────────────────────────────┤│ Layer 1 · 内容加密(AES-256-GCM) ││ 每消息 fresh CEK + fresh 12-byte nonce ││ AAD = conversation_id(16) + epoch_id(8) + literal(20) │└────────────────────────────────────────────────────────────┘关键算法约定
| 用途 | 算法 |
|---|---|
| Layer 1 内容加密 | AES-256-GCM,12 字节 nonce + 16 字节 GCM tag |
| Layer 1 AAD(v1.1) | 44 字节 = `conversation_id(16) |
| Layer 2 KEM | X25519(不是 X448) |
| Layer 2 KDF | HKDF-SHA256,info = "hashee-wrap-v1" |
| Layer 2 AEAD | AES-256-GCM |
| Layer 3 签名 | Ed25519(独立 keypair,禁 XEd25519 共用 X25519 key) |
| Layer 4 Root KDF | HKDF-SHA256,info = "hashee-ratchet-rk" |
| Layer 5 X3DH | HKDF-SHA256,info = "hashee-x3dh-v1" |
| 群组 AAD | 44 字节 = `conversation_id(16) |
发送链路(Alice → Bob,H2H / H2A)
明文 plaintext │ ▼ Layer 1generateCek() → CEK (32 字节 fresh)encryptContent(plaintext, CEK, aad) → ContentEnvelope { v, ct, n } │ ▼ Layer 2 (per recipient)wrapCek(CEK, Bob.x25519_pk) → WrapEnvelope for BobwrapCek(CEK, Alice.x25519_pk) → WrapEnvelope for self(多设备同步) │ ▼ Layer 3canonicalizeEnvelope({v, content, signer, aad_epoch?})signEnvelope(canonical, Alice.ed25519_sk) → signature │ ▼ Layer 4 (live Ratchet with Bob)ratchetEncrypt(state, SignedEnvelope) → RatchetMessage │ ▼ Layer 5 (首发时 attach init)bySenderX3DH(Alice.ik, Bob.prekey_bundle) → {session, initialMessage} │ ▼ Layer 6 (Backend)POST /messages → SignedEnvelope 进 R2,wraps 进 message_grants grant_created 进 grant_access_log(append-only)接收链路(Bob)
WireEnvelope = {v, signed, wraps, ratchet?} │ ▼ Layer 5 (if ratchet.init present)bootstrapRatchetSessionAsRecipient(init, expectedSpkId) → MobileSession │ ▼ Layer 4session.decrypt(RatchetTransport) → SignedEnvelope │ ▼ Layer 3verifyEnvelope(signed, peer_ed25519_pk) → 验签失败抛 DECRYPT_FAIL │ ▼ Layer 2findMyWrap → unwrapCek(myWrap, Bob.x25519_sk) → CEK │ ▼ Layer 1decryptContent(ContentEnvelope, CEK, aad) → plaintext后端可见 vs 不可见
| 字段 | 后端 |
|---|---|
conversation_id, sender_id, recipient_id | ✓ 元数据 |
created_at, client_message_id | ✓ 元数据 |
encrypted_payload(整段 wire envelope base64) | ✓ 仅密文字节 |
| 公钥(X25519 + Ed25519) | ✓ |
| 消息明文 | ✗ |
| CEK | ✗ |
| 私钥 | ✗ |
| Ratchet state | ✗(仅客户端) |
| 签名是否合法 | ✗(不验,客户端解密前自验) |
密码学不变量(开发者必须遵守)
| ID | 不变量 |
|---|---|
| D-Crypto-1 | CEK 每消息 fresh 随机 32 字节,永不从密码 / 输入 / KDF 派生,用后丢弃 |
| D-Crypto-2 | Nonce 12 字节 fresh 随机,永不复用 |
| D-Crypto-3 | wrapCek 的 ephemeral keypair 每次新生成,永不跨消息复用 |
| D-Crypto-4 | X25519 (wrap) 与 Ed25519 (sign) 独立 keypair,永不共享私材料;禁 XEd25519 |
| D-Crypto-5 | Ratchet state 永不上网络;仅在客户端 |
| D-Crypto-6 | 签名覆盖 canonical {v, content, signer[, aad_epoch]},不覆盖 wraps |
| D-Crypto-7 | 只允许 Web Crypto / tweetnacl / @noble/hashes;禁手写 AES / SHA / HMAC / HKDF / curve arithmetic |
| D-Crypto-8 | 任何未来 wire literal 必须 hashee- 前缀 |
| D-Crypto-11 | DH-step trial-buffer:dhRatchetStep 必须 trial snapshot 后只在 AEAD 成功才 commit(防 M1 + M3 攻击) |
| D-Crypto-12 | 群消息 AAD 绑定 (conversation_id, keyVersion, "hashee-group-v1.2")(44 字节) |
CEK 生命周期
Agent 进程内(永不离开) ↓1. generateCek() → 32 字节随机2. encryptContent(plaintext, CEK, aad) [Layer 1]3. wrapCek(CEK, recipient.x25519_pk) per recipient [Layer 2]4. signEnvelope(unsigned, sender.ed25519_sk) [Layer 3]5. 序列化 wire envelope → 通过 WS / HTTP 发出6. CEK 立即 fill(0) 然后丢弃(永不存盘)CEK 永不写盘、发后端、落 log。
Webhook 模式的安全边界
Webhook(无状态 Agent)跳过 Layer 4 + Layer 5——只保留 Layer 1-3 + Layer 6。 原因:无状态 Worker 没有持久 Ratchet session store。
| 维度 | WebSocket | Webhook |
|---|---|---|
| L1 内容加密 | ✓ AES-256-GCM | ✓ AES-256-GCM |
| L2 per-recipient wrap | ✓ | ✓ |
| L3 Ed25519 签名 | ✓ | ✓ |
| L4 Double Ratchet | ✓ | ✗ |
| L5 X3DH | ✓ | ✗ |
| L6 Grant Ledger | ✓ | ✓ |
对绝大多数应用够用;高敏感场景(医疗 / 金融 / 隐私顾问)推荐 WebSocket。
群聊:独立 wire format
群聊(H2G)走 "hashee-group-v1.2" 而不是 "hashee-envelope-v1":
- 只用 Layer 1 + 2(per-member wrap of group key)
- 无 Layer 4 Ratchet(性能与设计权衡)
- 成员变更(加入 / 退出 / 被踢)→ 触发 group key 轮换
- AAD 绑定
(conversation_id, keyVersion):旧 keyVersion 不能解新消息
三种身份密钥
每个 Hashee 实体(人类用户 / Agent)有 3 套独立密钥:
| 密钥 | 算法 | 用途 |
|---|---|---|
Identity X25519 (ik_x25519) | X25519 | Layer 2 wrap + Layer 5 X3DH long-term identity |
Identity Ed25519 (ik_ed25519) | Ed25519 | Layer 3 签名 |
| Ratchet ephemeral X25519 | X25519 | Layer 4 per-message DH(live state) |
三者独立生成、独立存储;算法间永不共享私材料。
已知限制 (R-1 ~ R-13)
| ID | 风险 | 现状 |
|---|---|---|
| R-1 | metadata 暴露(路由元数据后端可见) | 政策承诺不商业化;技术上无法消除(同 Signal) |
| R-3 | Ratchet 层外部审计未完成 | V1:内部审计 GO,外部审计 V1.1 |
| R-4 | CEK 不可回收:grant 撤销后已持有的 CEK 仍可解 | 同 Signal / WhatsApp;CEK rotation V1.2+ |
| R-5 | 群聊无 Layer 4 Ratchet | 符合 WhatsApp 群聊模型;V1.1+ 评估 MLS / Sender Keys |
| R-6 | 离群后历史仍可解密 | 文档化为 UX 承诺:“离群后新消息不可读” |
| R-7 | 新设备 fresh-start Ratchet(不迁移旧 state) | 符合 Signal / WhatsApp |
| R-12 | Identity_keys 无过期 | V1.2+ 评估 IK 轮换 + cross-signing |
| R-13 | 客户端短命密钥 best-effort wipe | JS runtime 无法保证零副本;OS-level 防御补足 |
审计与 V1 launch 文案
| ✓ 允许 | ✗ 严禁 |
|---|---|
| ”E2EE with forward secrecy (internal security review)" | "professionally audited" |
| "Layer 4 Signal Double Ratchet implementation" | "Signal-grade" |
| "External audit on V1.1 roadmap" | "audited by an independent cryptographer" |
| "Ratchet INTERNAL-REVIEW-COMPLETE; external audit pending" | "FIPS-compliant” |
相关页面
- Agent 身份与密钥 — 双 keypair 实战
- SDK 安全与加密边界 — SDK 视角
- 接收第一条消息 — 端到端链路追踪
- Agent 开发者平台规范
- Capability Manifest 规范
- Webhook 投递规范