端到端加密(E2EE)

MorisBox 会加密应用内内容,因此服务器只存储密文和元数据。私钥永远不会离开用户或客服人员的设备。

加密范围

内容E2EE
支持对话(用户 ↔ 合作伙伴客服人员)
标记为 encryption: "e2e" 的中心消息
E2EE 信封内的媒体
OTP / SMS / WhatsApp 备用投递正文(这些渠道必须能够读取正文)
元数据(合作伙伴、时间戳、状态和大小)

E2E 消息的内容仅存储于应用内。如果用户在应用中不在线(大约五分钟没有设备活动),MorisBox 还会发送一条不含正文的 WhatsApp 提醒,随后通过 SMS 提醒:

您的 MorisBox 收件箱收到了来自 {partner} 的新消息。请打开应用阅读(安全消息)。

此提醒绝不会包含加密正文。OTP 和登录验证码不使用 E2EE,因此用户始终可以访问自己的账户。

协议(v1)

元素算法
设备身份X25519
密钥协商ECDH(X25519)
KDFHKDF-SHA-256
内容加密AES-256-GCM
传输编码Base64url
content_key  →  AES-GCM encrypt(plaintext JSON)
wrap(content_key) per recipient device via ECDH + AES-GCM

v1 不包含 Signal 风格的双棘轮;该安全增强已列入计划。

设备密钥目录

用户设备

登录后,客户端生成身份密钥对,并发布其中的公钥

POST /api/v1/me/devices/keys
Authorization: Bearer <user_jwt>
Content-Type: application/json

{
  "identity_key_pub": "<b64url x25519 public key>"
}

合作伙伴客服人员设备(合作伙伴中心)

POST /api/v1/agent/devices/keys
Authorization: Bearer <partner_api_key>

{
  "device_uuid": "<browser session uuid>",
  "identity_key_pub": "<b64url>"
}

获取手机号码的密钥

GET /api/v1/keys/+228XXXXXXXX
Authorization: Bearer <partner_api_key>
{
  "phone": "+228XXXXXXXX",
  "devices": [
    {
      "device_uuid": "...",
      "identity_key_pub": "...",
      "platform": "web",
      "has_keys": true
    }
  ],
  "agent_devices": [ ... ]
}

发送加密中心消息

POST /api/v1/messages
Authorization: Bearer <partner_api_key>
Content-Type: application/json

{
  "phone": "+228XXXXXXXX",
  "encryption": "e2e",
  "e2e_envelope": {
    "v": 1,
    "alg": "x25519-hkdf-sha256-aes-256-gcm",
    "mode": "sealed",
    "key_version": 0,
    "sender_device_uuid": "partner-agent-…",
    "ciphertext": "<b64url>",
    "nonce": "<b64url>",
    "aad": "seed360|msg|<ref>|v1",
    "key_wraps": [
      {
        "device_uuid": "user-device-…",
        "wrap": "<b64url>",
        "wrap_nonce": "<b64url>",
        "sender_eph_pub": "<b64url>"
      }
    ]
  },
  "content_type": "text"
}

服务器只存储信封。body 绝不会以明文形式持久化。

错误:

代码含义
e2e_no_devices用户尚未发布身份密钥
validation缺少 e2e_envelope.ciphertext / nonce

对话消息(客户端)

POST /api/v1/me/conversations/<ref>/messages
Authorization: Bearer <user_jwt>

{
  "encrypted": true,
  "e2e_mode": "conversation",
  "e2e_key_version": 3,
  "e2e_envelope": { ... }
}

上传内容密钥包装:

POST /api/v1/me/conversations/<ref>/keys
{
  "key_version": 3,
  "wraps": [
    {
      "device_uuid": "...",
      "device_kind": "user",
      "wrap": "...",
      "wrap_nonce": "...",
      "sender_eph_pub": "..."
    }
  ]
}

对等设备目录:

GET /api/v1/me/conversations/<ref>/devices

客户端行为

客户端行为
客户端应用 / 移动端登录时生成密钥;加密聊天;解密信封并显示
合作伙伴中心发布客服人员密钥;加密回复 / 加密中心消息
服务器路由密文;永不解密

UI 会在加密消息上显示挂锁标签。如果设备没有对应的密钥包装,用户会看到“无法解密”。

安全注意事项

  • 私钥保留在设备上(SecureStore / 本地存储;如有条件,应逐步采用硬件支持的密钥)。
  • AAD 将信封绑定到消息上下文字符串,以降低替换攻击风险。
  • 已撤销设备不会包含在新的密钥包装中。
  • 切勿在服务器或网关上记录解密后的正文。

另请参阅