mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-07-03 00:50:22 +08:00
502 lines
19 KiB
Markdown
502 lines
19 KiB
Markdown
# 基于 Virgil Security E3Kit 的单聊 & 群聊最小落地方案
|
||
|
||
## 一、核心设计原则
|
||
|
||
| 原则 | 说明 |
|
||
|---|---|
|
||
| **服务端零知情** | 服务端只接触密文、元数据与业务控制,永远不触碰明文、私钥 |
|
||
| **客户端加解密** | 所有加密/解密/签名/验签均在客户端完成,使用 Virgil E3Kit SDK |
|
||
| **Virgil Cloud 托管公钥** | 用户公钥(Virgil Card)存储在 Virgil Cloud,服务端不保存 |
|
||
| **JWT 桥接认证** | 服务端用 Virgil App Key 签发 Virgil JWT,客户端持 JWT 与 Virgil Cloud 交互 |
|
||
|
||
## 二、整体架构图
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph Client["客户端 (iOS/Android/Web)"]
|
||
C1["E3Kit SDK"]
|
||
C2["IM SDK"]
|
||
C3["本地密钥存储"]
|
||
end
|
||
|
||
subgraph Server["OpenIM 服务端"]
|
||
S1["API Gateway"]
|
||
S2["Auth Service"]
|
||
S3["CryptoService\n(新增 gRPC)"]
|
||
S4["Msg Service"]
|
||
S5["Group Service"]
|
||
S6["Push Service"]
|
||
S7["Sync / MsgTransfer"]
|
||
DB["MongoDB / Redis"]
|
||
end
|
||
|
||
subgraph Virgil["Virgil Cloud"]
|
||
V1["Cards Service\n(公钥目录)"]
|
||
V2["Keyknox\n(密钥备份)"]
|
||
V3["Group Tickets\n(群密钥票据)"]
|
||
end
|
||
|
||
C2 -->|"WebSocket / HTTP\n(密文 envelope)"| S1
|
||
S1 --> S2
|
||
S1 --> S3
|
||
S1 --> S4
|
||
S1 --> S5
|
||
S4 --> S7
|
||
S7 --> DB
|
||
S4 --> S6
|
||
|
||
C1 -->|"Virgil JWT"| V1
|
||
C1 -->|"密钥备份/恢复"| V2
|
||
C1 -->|"群票据同步"| V3
|
||
|
||
S3 -->|"签发 JWT\n(Virgil App Key)"| C1
|
||
S5 -->|"群事件通知"| S6
|
||
```
|
||
|
||
**图意说明:**
|
||
|
||
1. 客户端持有 E3Kit SDK(负责加解密)和 IM SDK(负责业务通信),私钥存储在设备本地。
|
||
2. 服务端新增 `CryptoService` gRPC 服务,核心职责是签发 Virgil JWT 和管理群密钥版本号。
|
||
3. Virgil Cloud 承担公钥目录、密钥备份、群加密票据的托管。
|
||
4. 消息流:客户端加密 -> 密文 envelope 经 WebSocket/HTTP 到服务端 -> 服务端存储密文+路由 -> 接收端拉取密文 -> 客户端解密。
|
||
5. 服务端全程不接触明文,只处理密文 bytes 和元数据。
|
||
|
||
## 三、单聊方案
|
||
|
||
### 3.1 单聊加密模型
|
||
|
||
单聊使用 E3Kit 的 **Default Encryption**(最小 MVP)或 **Double Ratchet**(增强版,提供前向保密)。
|
||
|
||
MVP 阶段推荐 Default Encryption:
|
||
|
||
| 特性 | Default Encryption | Double Ratchet |
|
||
|---|---|---|
|
||
| 前向保密 | 否 | 是 |
|
||
| 实现复杂度 | 低 | 中 |
|
||
| 平台支持 | JS/Swift/Kotlin | Swift/Kotlin(JS 暂不支持) |
|
||
| 适用场景 | MVP 快速落地 | 安全性要求高的正式版 |
|
||
|
||
### 3.2 单聊时序图
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant A as Alice (客户端)
|
||
participant S as OpenIM Server
|
||
participant CS as CryptoService
|
||
participant VC as Virgil Cloud
|
||
participant B as Bob (客户端)
|
||
|
||
Note over A,B: === 阶段 1: 初始化 (首次登录) ===
|
||
|
||
A->>S: POST /auth/login
|
||
S-->>A: access_token
|
||
|
||
A->>CS: RegisterDevice(userID, deviceID)
|
||
CS-->>A: DeviceInfo
|
||
|
||
A->>CS: GetVirgilJWT(userID, deviceID)
|
||
CS-->>A: virgil_jwt
|
||
|
||
A->>VC: E3Kit.init(virgilJWT) -> register()
|
||
VC-->>A: 生成密钥对, 发布公钥 Card
|
||
|
||
Note over A,B: === 阶段 2: Alice 给 Bob 发加密消息 ===
|
||
|
||
A->>VC: findUsers(["bob"])
|
||
VC-->>A: Bob 的公钥 Card
|
||
|
||
A->>A: E3Kit.authEncrypt("Hello", bobCard)
|
||
Note right of A: 用 Bob 公钥加密 + Alice 私钥签名
|
||
|
||
A->>S: SendMsg(密文 envelope)
|
||
Note right of A: MsgData.content = ciphertext bytes<br/>MsgData.contentType = E2EE_TEXT<br/>MsgData.ex = {"cipher_suite":"ed25519/aes256-gcm"}
|
||
|
||
S->>S: 校验身份/会话/幂等<br/>分配 serverMsgID + seq<br/>存储密文到 MongoDB
|
||
S-->>A: SendMsgResp(serverMsgID, seq)
|
||
|
||
S->>B: WebSocket push: message.new
|
||
Note right of S: 推送不含明文
|
||
|
||
Note over A,B: === 阶段 3: Bob 拉取并解密 ===
|
||
|
||
B->>S: PullMessageBySeqs(seq)
|
||
S-->>B: MsgData(密文 envelope)
|
||
|
||
B->>VC: findUsers(["alice"])
|
||
VC-->>B: Alice 的公钥 Card
|
||
|
||
B->>B: E3Kit.authDecrypt(ciphertext, aliceCard)
|
||
Note right of B: 用 Bob 私钥解密 + Alice 公钥验签
|
||
B->>B: 显示明文 "Hello"
|
||
```
|
||
|
||
**关键说明:**
|
||
|
||
- 边界条件:`findUsers` 结果应在客户端缓存,避免每条消息都查询 Virgil Cloud。
|
||
- 异常路径:若 Bob 的 Card 不存在(未注册 E3Kit),消息无法加密,客户端应提示“对方尚未启用加密”。
|
||
- 幂等性:消息幂等键 = `sendID + deviceID + clientMsgID`,服务端去重。
|
||
- 性能:E3Kit 加密单条消息主要是本地计算,通常瓶颈在网络链路而非加密。
|
||
|
||
### 3.3 单聊消息 Envelope 结构
|
||
|
||
消息体复用 OpenIM 现有的 `sdkws.MsgData`,加密信息通过现有字段承载:
|
||
|
||
```protobuf
|
||
message MsgData {
|
||
string sendID = 1;
|
||
string recvID = 2;
|
||
string clientMsgID = 4;
|
||
int32 sessionType = 9; // 1=单聊
|
||
int32 contentType = 11; // 新增: 2001=E2EE_TEXT, 2002=E2EE_IMAGE, ...
|
||
bytes content = 12; // 密文 ciphertext (E3Kit 加密输出)
|
||
string ex = 23; // JSON: {"envelope_version":1, "cipher_suite":"ed25519/aes256-gcm"}
|
||
// ... 其余字段不变
|
||
}
|
||
```
|
||
|
||
不需要修改 proto 定义,只需约定 `contentType` 新值和 `ex` 字段的 JSON schema。
|
||
|
||
## 四、群聊方案
|
||
|
||
### 4.1 群聊加密模型
|
||
|
||
群聊使用 E3Kit 的 **Group Encryption**:
|
||
|
||
- 群主创建群时通过 `E3Kit.createGroup(groupId, members)` 生成群共享密钥票据。
|
||
- 票据存储在 Virgil Cloud,群成员通过 `E3Kit.loadGroup(groupId, ownerCard)` 加载。
|
||
- 新成员加入后通过 `group.add(newMemberCard)` 获得访问历史消息的能力。
|
||
- 成员移除后通过 `group.remove(memberCard)` 撤销访问权限,群密钥自动轮转。
|
||
|
||
### 4.2 群聊时序图 — 建群与首条消息
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Owner as 群主 Alice
|
||
participant S as OpenIM Server
|
||
participant GS as Group Service
|
||
participant CS as CryptoService
|
||
participant VC as Virgil Cloud
|
||
participant M1 as 成员 Bob
|
||
participant M2 as 成员 Carol
|
||
|
||
Note over Owner,M2: === 阶段 1: 创建群 ===
|
||
|
||
Owner->>GS: CreateGroup({members:[Bob,Carol]})
|
||
GS->>GS: 创建群记录, group_key_version=1
|
||
GS-->>Owner: {groupID, group_key_version:1}
|
||
|
||
Owner->>VC: findUsers(["bob","carol"])
|
||
VC-->>Owner: Bob & Carol 的 Cards
|
||
|
||
Owner->>VC: E3Kit.createGroup(groupID, [bobCard, carolCard])
|
||
Note right of Owner: 生成群共享密钥<br/>票据上传 Virgil Cloud
|
||
VC-->>Owner: Group 对象
|
||
|
||
GS->>M1: 推送 group.created 通知
|
||
GS->>M2: 推送 group.created 通知
|
||
|
||
Note over Owner,M2: === 阶段 2: 成员加载群密钥 ===
|
||
|
||
M1->>VC: findUsers(["alice"])
|
||
VC-->>M1: Alice (Owner) 的 Card
|
||
M1->>VC: E3Kit.loadGroup(groupID, aliceCard)
|
||
VC-->>M1: Group 对象 (本地缓存)
|
||
|
||
M2->>VC: E3Kit.loadGroup(groupID, aliceCard)
|
||
VC-->>M2: Group 对象
|
||
|
||
Note over Owner,M2: === 阶段 3: Alice 发送群加密消息 ===
|
||
|
||
Owner->>Owner: group.encrypt("大家好!")
|
||
Owner->>S: SendMsg(密文 envelope, groupID, group_key_version=1)
|
||
S->>S: 校验 Alice 是群成员<br/>group_key_version 合法<br/>存储密文, 分配 seq
|
||
S-->>Owner: SendMsgResp
|
||
|
||
S->>M1: WebSocket push
|
||
S->>M2: WebSocket push
|
||
|
||
M1->>S: PullMessageBySeqs
|
||
S-->>M1: MsgData(密文)
|
||
M1->>VC: findUsers(["alice"])
|
||
M1->>M1: group.decrypt(ciphertext, aliceCard)
|
||
M1->>M1: 显示 "大家好!"
|
||
```
|
||
|
||
### 4.3 群成员变更与密钥轮转时序图
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Owner as 群主 Alice
|
||
participant S as OpenIM Server
|
||
participant GS as Group Service
|
||
participant CS as CryptoService
|
||
participant VC as Virgil Cloud
|
||
participant New as 新成员 Dan
|
||
participant M1 as 成员 Bob
|
||
|
||
Note over Owner,M1: === 加人场景 ===
|
||
|
||
Owner->>GS: InviteUserToGroup(groupID, [Dan])
|
||
GS->>GS: 添加 Dan 到群成员
|
||
GS->>CS: BumpGroupKeyVersion(groupID, "member_added")
|
||
CS-->>GS: {group_key_version: 2}
|
||
GS-->>Owner: {group_key_version: 2}
|
||
|
||
Owner->>VC: findUsers(["dan"])
|
||
VC-->>Owner: Dan 的 Card
|
||
Owner->>VC: group.add(danCard)
|
||
Note right of Owner: Virgil Cloud 更新群票据<br/>Dan 可解密历史消息
|
||
|
||
GS->>New: 推送 group.member_changed
|
||
GS->>M1: 推送 group.member_changed (含新 version)
|
||
|
||
New->>VC: E3Kit.loadGroup(groupID, ownerCard)
|
||
VC-->>New: Group 对象
|
||
|
||
M1->>VC: group.update()
|
||
Note right of M1: 拉取最新群票据
|
||
|
||
Note over Owner,M1: === 踢人场景 ===
|
||
|
||
Owner->>GS: KickGroupMember(groupID, [Dan])
|
||
GS->>GS: 移除 Dan
|
||
GS->>CS: BumpGroupKeyVersion(groupID, "member_removed")
|
||
CS-->>GS: {group_key_version: 3}
|
||
|
||
Owner->>VC: group.remove(danCard)
|
||
Note right of Owner: Dan 无法再 loadGroup<br/>后续消息 Dan 无法解密
|
||
|
||
GS->>M1: 推送 group.member_changed
|
||
M1->>VC: group.update()
|
||
```
|
||
|
||
**关键说明:**
|
||
|
||
- 群密钥版本:每次成员变更,服务端 `group_key_version` +1,客户端据此判断是否需要 `group.update()`。
|
||
- 加人:新成员可解密加入前的历史消息(E3Kit Group 设计)。
|
||
- 踢人:被踢成员无法解密踢出后的新消息,但仍可解密踢出前已获取的消息(E2EE 的固有限制)。
|
||
- 并发:多个管理员同时操作成员时,`BumpGroupKeyVersion` 使用数据库原子递增保证版本一致。
|
||
- 性能:`group.update()` 涉及一次 Virgil Cloud 请求,建议客户端在收到 `group.member_changed` 通知后异步执行。
|
||
|
||
## 五、服务端接口清单
|
||
|
||
### 5.1 CryptoService(新增 gRPC 服务)
|
||
|
||
基于已有 `protocol/crypto/crypto.proto` 定义:
|
||
|
||
| RPC 接口 | 请求 | 响应 | 职责说明 |
|
||
|---|---|---|---|
|
||
| `RegisterDevice` | `RegisterDeviceReq` | `RegisterDeviceResp` | 注册设备,建立 `userID -> deviceID -> virgilIdentity` 映射 |
|
||
| `GetDevices` | `GetDevicesReq` | `GetDevicesResp` | 查询用户所有已注册设备 |
|
||
| `RevokeDevice` | `RevokeDeviceReq` | `RevokeDeviceResp` | 吊销设备,标记为 inactive |
|
||
| `GetVirgilJWT` | `GetVirgilJWTReq` | `GetVirgilJWTResp` | 为合法设备签发 Virgil JWT(核心接口) |
|
||
| `GetGroupKeyVersion` | `GetGroupKeyVersionReq` | `GetGroupKeyVersionResp` | 查询群当前密钥版本号 |
|
||
| `BumpGroupKeyVersion` | `BumpGroupKeyVersionReq` | `BumpGroupKeyVersionResp` | 群成员变更时递增密钥版本(Group Service 内部调用) |
|
||
| `GetGroupKeyEvents` | `GetGroupKeyEventsReq` | `GetGroupKeyEventsResp` | 查询密钥版本变更历史(客户端增量同步) |
|
||
| `SecurityPrecheck` | `SecurityPrecheckReq` | `SecurityPrecheckResp` | 安全前置校验(设备状态/风控) |
|
||
| `IntegrityReport` | `IntegrityReportReq` | `IntegrityReportResp` | 设备完整性上报 |
|
||
|
||
### 5.2 现有服务需要的改动
|
||
|
||
#### Auth Service
|
||
|
||
| 改动点 | 说明 |
|
||
|---|---|
|
||
| 登录响应增加字段 | 在 `ex` 或扩展字段中返回 `e2ee_enabled: true`,提示客户端初始化 E3Kit |
|
||
|
||
#### Msg Service
|
||
|
||
| 改动点 | 说明 |
|
||
|---|---|
|
||
| `SendMsg` 校验逻辑 | 当 `contentType` 位于 E2EE 区间时,跳过明文内容校验,仅校验 ciphertext 长度上限 |
|
||
| 消息存储 | `content` 字段直接存储密文 bytes,沿用现有存储路径 |
|
||
| 推送通知 | 推送 payload 中不携带 `content`,仅携带 `conversationID`、`senderNickname`、占位提示 |
|
||
|
||
#### Group Service
|
||
|
||
| 改动点 | 说明 |
|
||
|---|---|
|
||
| `CreateGroup` | 创建群时初始化 `group_key_version = 1` |
|
||
| `InviteUserToGroup` | 成功后调用 `CryptoService.BumpGroupKeyVersion(eventType="member_added")` |
|
||
| `KickGroupMember` | 成功后调用 `CryptoService.BumpGroupKeyVersion(eventType="member_removed")` |
|
||
| `QuitGroup` | 成功后调用 `CryptoService.BumpGroupKeyVersion(eventType="member_left")` |
|
||
| 通知 payload | `group.member_changed` 通知中携带最新 `group_key_version` |
|
||
|
||
### 5.3 服务端 HTTP API(Gateway 暴露)
|
||
|
||
```text
|
||
POST /api/v1/crypto/device/register -> CryptoService.RegisterDevice
|
||
GET /api/v1/crypto/devices -> CryptoService.GetDevices
|
||
POST /api/v1/crypto/device/revoke -> CryptoService.RevokeDevice
|
||
POST /api/v1/crypto/virgil-jwt -> CryptoService.GetVirgilJWT
|
||
GET /api/v1/crypto/group-key-version -> CryptoService.GetGroupKeyVersion
|
||
POST /api/v1/crypto/group-key-version/bump -> CryptoService.BumpGroupKeyVersion
|
||
GET /api/v1/crypto/group-key-events -> CryptoService.GetGroupKeyEvents
|
||
POST /api/v1/crypto/security-precheck -> CryptoService.SecurityPrecheck
|
||
POST /api/v1/crypto/integrity-report -> CryptoService.IntegrityReport
|
||
```
|
||
|
||
## 六、客户端接口清单
|
||
|
||
### 6.1 E3Kit 封装层接口
|
||
|
||
| 接口 | 输入 | 输出 | 说明 |
|
||
|---|---|---|---|
|
||
| `initialize(tokenCallback)` | JWT 获取回调 | void | 初始化 E3Kit,设置 JWT 刷新回调 |
|
||
| `register()` | - | void | 首次注册:生成密钥对,发布 Virgil Card |
|
||
| `restorePrivateKey(password)` | 备份密码 | void | 从 Virgil Keyknox 恢复私钥(换设备场景) |
|
||
| `backupPrivateKey(password)` | 备份密码 | void | 备份私钥到 Virgil Keyknox |
|
||
| `findUsers(userIDs)` | 用户 ID 列表 | Map<ID, Card> | 批量查找用户公钥,结果缓存 |
|
||
| `cleanup()` | - | void | 登出时清理本地私钥 |
|
||
| `rotatePrivateKey()` | - | void | 私钥泄露时轮换密钥对 |
|
||
|
||
### 6.2 单聊加解密接口
|
||
|
||
| 接口 | 输入 | 输出 | 说明 |
|
||
|---|---|---|---|
|
||
| `encryptForUser(plaintext, recipientCard)` | 明文 + 接收者 Card | 密文 string | 用接收者公钥加密 + 发送者私钥签名 |
|
||
| `decryptFromUser(ciphertext, senderCard)` | 密文 + 发送者 Card | 明文 string | 用本地私钥解密 + 发送者公钥验签 |
|
||
| `encryptFileForUser(inputStream, recipientCard)` | 文件流 + Card | 加密流 | 大文件加密 |
|
||
| `decryptFileFromUser(inputStream, senderCard)` | 加密流 + Card | 明文流 | 大文件解密 |
|
||
|
||
### 6.3 群聊加解密接口
|
||
|
||
| 接口 | 输入 | 输出 | 说明 |
|
||
|---|---|---|---|
|
||
| `createGroup(groupID, memberCards)` | 群 ID + 成员 Cards | Group 对象 | 群主创建群加密上下文 |
|
||
| `loadGroup(groupID, ownerCard)` | 群 ID + 群主 Card | Group 对象 | 非群主加载群加密上下文 |
|
||
| `getGroup(groupID)` | 群 ID | Group / null | 从本地缓存获取群对象 |
|
||
| `updateGroup(groupID)` | 群 ID | void | 拉取最新群票据(成员变更后调用) |
|
||
| `addGroupMember(groupID, newMemberCard)` | 群 ID + 新成员 Card | void | 群主添加成员到加密上下文 |
|
||
| `removeGroupMember(groupID, memberCard)` | 群 ID + 成员 Card | void | 群主从加密上下文移除成员 |
|
||
| `deleteGroup(groupID)` | 群 ID | void | 群主删除群加密上下文 |
|
||
| `encryptForGroup(plaintext, group)` | 明文 + Group | 密文 string | 群消息加密 |
|
||
| `decryptFromGroup(ciphertext, senderCard, group)` | 密文 + 发送者 Card + Group | 明文 string | 群消息解密 + 验签 |
|
||
|
||
### 6.4 IM SDK 业务层接口
|
||
|
||
| 接口 | 说明 |
|
||
|---|---|
|
||
| `requestVirgilJWT()` | 调用服务端 `/crypto/virgil-jwt`,获取并缓存 Virgil JWT |
|
||
| `registerDevice()` | 调用服务端 `/crypto/device/register` |
|
||
| `sendEncryptedMessage(conversationID, plaintext)` | 加密 -> 构造 `MsgData`(E2EE contentType) -> `SendMsg` |
|
||
| `onReceiveEncryptedMessage(msgData)` | 判断 contentType -> 查找 senderCard -> 解密 -> 回调 UI |
|
||
| `onGroupMemberChanged(groupID, newVersion)` | 收到通知后调用 `updateGroup()` 刷新群票据 |
|
||
| `syncGroupKeyVersion(groupID)` | 调用服务端 `/crypto/group-key-version`,对比本地版本决定是否更新 |
|
||
|
||
## 七、数据模型(服务端新增表)
|
||
|
||
```mermaid
|
||
erDiagram
|
||
DEVICE ||--|| USER : belongs_to
|
||
DEVICE {
|
||
string device_id PK
|
||
string user_id FK
|
||
string platform
|
||
string device_model
|
||
string app_version
|
||
string virgil_identity
|
||
string status "active / revoked"
|
||
int64 last_seen_at
|
||
int64 create_time
|
||
}
|
||
|
||
GROUP_KEY_VERSION ||--|| GROUP : tracks
|
||
GROUP_KEY_VERSION {
|
||
string group_id PK
|
||
int64 group_key_version "原子递增"
|
||
}
|
||
|
||
GROUP_KEY_EVENT }o--|| GROUP : belongs_to
|
||
GROUP_KEY_EVENT {
|
||
string event_id PK
|
||
string group_id FK
|
||
int64 group_key_version
|
||
string event_type "member_added/removed/left"
|
||
string operator_user_id
|
||
int64 create_time
|
||
}
|
||
```
|
||
|
||
## 八、contentType 约定
|
||
|
||
复用 OpenIM 现有 `contentType` 编码空间,为 E2EE 消息分配新区间:
|
||
|
||
| contentType | 名称 | 说明 |
|
||
|---|---|---|
|
||
| 2001 | `E2EE_TEXT` | 端到端加密文本 |
|
||
| 2002 | `E2EE_IMAGE` | 端到端加密图片(密文 content + 加密缩略图) |
|
||
| 2003 | `E2EE_VIDEO` | 端到端加密视频 |
|
||
| 2004 | `E2EE_FILE` | 端到端加密文件 |
|
||
| 2005 | `E2EE_AUDIO` | 端到端加密语音 |
|
||
| 2006 | `E2EE_LOCATION` | 端到端加密位置 |
|
||
| 2099 | `E2EE_CUSTOM` | 端到端加密自定义消息 |
|
||
|
||
`ex` 字段 JSON schema:
|
||
|
||
```json
|
||
{
|
||
"envelope_version": 1,
|
||
"cipher_suite": "ed25519/aes256-gcm",
|
||
"group_key_version": 2,
|
||
"sender_device_id": "ios_a1"
|
||
}
|
||
```
|
||
|
||
## 九、实施路线(分两个阶段)
|
||
|
||
### 阶段 1:单聊 MVP(约 2-3 周)
|
||
|
||
```text
|
||
服务端:
|
||
├── 实现 CryptoService gRPC (internal/rpc/crypto/)
|
||
│ ├── RegisterDevice / GetDevices / RevokeDevice
|
||
│ ├── GetVirgilJWT (核心: 用 Virgil App Key 签发)
|
||
│ └── SecurityPrecheck
|
||
├── API Gateway 新增 /crypto/* 路由
|
||
├── Msg Service: E2EE contentType 跳过明文校验
|
||
└── Push Service: E2EE 消息推送不含 content
|
||
|
||
客户端:
|
||
├── 集成 E3Kit SDK
|
||
├── 实现 E2EEManager (initialize/register/findUsers)
|
||
├── 实现单聊 encryptForUser / decryptFromUser
|
||
├── IM SDK 封装 sendEncryptedMessage / onReceiveEncryptedMessage
|
||
└── UI: 加密消息标识 (锁图标)
|
||
```
|
||
|
||
### 阶段 2:群聊(约 2-3 周)
|
||
|
||
```text
|
||
服务端:
|
||
├── CryptoService 补充: GetGroupKeyVersion / BumpGroupKeyVersion / GetGroupKeyEvents
|
||
├── Group Service 联动: 成员变更时 BumpGroupKeyVersion
|
||
├── GROUP_KEY_VERSION / GROUP_KEY_EVENT 表
|
||
└── 通知 payload 携带 group_key_version
|
||
|
||
客户端:
|
||
├── E2EEManager 补充群聊接口 (createGroup/loadGroup/addMember/removeMember)
|
||
├── 实现 encryptForGroup / decryptFromGroup
|
||
├── onGroupMemberChanged -> group.update()
|
||
└── 群聊 UI: 显示加密状态 / 密钥版本
|
||
```
|
||
|
||
## 十、安全风险与缓解
|
||
|
||
| 风险 | 影响 | 缓解措施 |
|
||
|---|---|---|
|
||
| 服务端日志泄露明文 | 破坏 E2EE 边界 | 服务端 `content` 字段日志脱敏,E2EE 类型消息禁止打印 `content` |
|
||
| 被吊销设备仍获取 JWT | 安全失控 | `GetVirgilJWT` 必须校验设备 `status=active` |
|
||
| 推送携带明文 | 绕过加密 | Push payload 仅含 `conversationID` + 占位提示 |
|
||
| 群成员变更后未更新群票据 | 用旧密钥加密 | 客户端发送前 `syncGroupKeyVersion`,版本不一致先 `update` |
|
||
| 私钥丢失 | 无法解密历史 | 引导用户 `backupPrivateKey`,换设备时 `restorePrivateKey` |
|
||
|
||
## 参考资料
|
||
|
||
- [Virgil Security Documentation](https://developer.virgilsecurity.com/)
|
||
- [E3Kit Quickstart](https://developer.virgilsecurity.com/docs/e3kit/get-started/quickstart)
|
||
- [Generate Client Tokens](https://developer.virgilsecurity.com/docs/e3kit/get-started/generate-client-tokens)
|
||
- [User Authentication](https://developer.virgilsecurity.com/docs/e3kit/user-authentication/)
|
||
- [Group Encryption](https://developer.virgilsecurity.com/docs/e3kit/end-to-end-encryption/group-chat)
|
||
- [Double Ratchet Encryption](https://developer.virgilsecurity.com/docs/e3kit/end-to-end-encryption/double-ratchet)
|