Files
im_manager/docs/admin-adfahi-research/服务端消息加密与内容审核改造文档.md
T
2026-09-13 23:39:18 +08:00

23 KiB
Raw Blame History

服务端消息加密与内容审核改造文档

文档状态:待开发
编制日期:2026-09-13
适用项目:im_manager(Admin Server)、im_server(IM Server)
本文档是后续数据库设计、后端开发、联调、迁移和验收的共同依据。

1. 建设目标

本次改造同时建设两项能力:

  1. 内容审核:IM Server 在保存或修改内容前,根据商户策略执行本地敏感词检查和阿里云文本内容审核。
  2. 消息加密存储:审核通过的消息在 IM Server 内部使用 AES-256-GCM 加密,国内 IM 业务库只保存密文;经授权的后台查询由 IM Server 解密后返回明文。

本次不做以下内容:

  • 不做客户端 AES 二次加密。
  • 不做端到端加密(E2EE)。
  • 不改变现有 HTTPS/WSS 传输协议。
  • 不允许 Admin Server 直接读取 IM 数据库并自行解密。
  • 不在日志、Kafka 事件、死信或监控标签中保存消息明文。

2. 安全边界和部署前提

2.1 部署分工

区域 系统 保存数据
国外 HotGo / Admin Server 商户审核策略、阿里云配置、字典根密钥、商户消息密钥
国内 IM Server 用户、会话、消息密文、审核结果和处置记录

该方案主要防护目标是:任意一侧数据库单独泄露时,不能直接恢复消息明文。

已知边界:HotGo 数据库、IM 数据库和完整代码同时泄露不在本方案的主要防护范围内。

2.2 传输安全

  • 客户端调用必须使用 https:// 和 wss://。
  • 阿里云审核请求必须使用 HTTPS。
  • Admin Server 与 IM Server 的内部配置和查询接口必须使用 HTTPS,并保留现有签名鉴权;跨公网部署时建议叠加 mTLS 或固定出口 IP 白名单。
  • TLS 只解决传输保护,AES-256-GCM 解决消息落库后的静态保护。

3. 总体架构

客户端
  | HTTPS/WSS明文业务数据(TLS通道内不可见)
  v
IM Server
  |-- 实时权限、宵禁、用户状态等校验
  |-- 获取商户审核策略
  |-- 本地敏感词补充检查
  |-- 阿里云 TextModerationPlus 审核
  |-- AES-256-GCM 加密
  `-- 密文写入国内IM库

Admin Server(国外HotGo)
  |-- 管理阿里云配置
  |-- 管理商户审核策略
  |-- 管理商户消息密钥
  |-- 通过内部接口下发配置/密钥
  `-- 通过IM内部查询接口查看解密后消息

4. 内容审核设计

4.1 审核范围

场景 阿里云 Service 审核时机
用户昵称 nickname_detection_pro 更新用户资料前
群昵称 nickname_detection_pro 更新群成员资料前
群名称 nickname_detection_pro 创建群或修改群资料前
用户签名 nickname_detection_pro 更新用户资料前
单聊文本 chat_detection_pro 消息入库及广播前
群聊文本 chat_detection_pro 消息入库及广播前
编辑后消息 chat_detection_pro 新版本入库前
群公告 chat_detection_pro 发布或修改前

图片、视频和文件继续走文件审核链路,不属于本文本审核接口范围。

4.2 商户范围

  • 第一阶段只为 tenant.code=default 的上架默认商户开启阿里云内容审核。
  • 其他团队商户默认关闭,不调用阿里云,不产生审核费用。
  • 数据模型保留按商户开启的能力,但只有平台管理员可变更。
  • 商户端管理员不得看到阿里云 AK/SK,不得修改平台级审核服务配置。

4.3 后台配置项

4.3.1 阿里云服务配置

复用 im_external_config 的 service=moderation,由平台管理员维护:

配置 说明
provider 固定为 aliyun_green
endpoint 默认 green-cip.cn-shanghai.aliyuncs.com
enabled 平台总开关
config_json.region_id 默认 cn-shanghai
config_json.nickname_service 默认 nickname_detection_pro
config_json.chat_service 默认 chat_detection_pro
config_json.timeout_ms 单次请求超时,建议 1000~2000 ms
config_json.failure_mode allow 或 block
secret_json.access_key_id 只写不回显
secret_json.access_key_secret 只写不回显

“检查配置”必须真实调用阿里云测试文本,不能只检查字段非空。返回仅包含成功与否、请求耗时、RequestID 和脱敏错误。

4.3.2 商户审核策略

可在 im_security_policy.sensitive_word_policy 中保存,避免新增重复策略表。JSON 结构固定为:

{
  "enabled": true,
  "aliyun_enabled": true,
  "local_words_enabled": true,
  "scenes": {
    "user_nickname": true,
    "user_signature": true,
    "group_name": true,
    "group_nickname": true,
    "private_message": true,
    "group_message": true,
    "group_announcement": true
  },
  "risk_actions": {
    "high": "block",
    "medium": "block",
    "low": "audit_only",
    "none": "allow"
  },
  "failure_mode": "allow",
  "block_message": "内容不符合平台规范,请修改后重试"
}

策略发布后使用现有 im.config.event 发布配置事件,IM Server 按商户清理本地缓存。

4.4 文本提取

审核输入不能直接对任意 JSON 执行字符串化,应按 Drafty 或消息类型提取用户可见文本:

  • 普通文本:提取完整文本。
  • Drafty 富文本:提取 txt 及按钮、链接等用户可见标题。
  • 图片/文件/语音:审核文件名、说明和附带文本,文件本体走文件审核。
  • 系统消息、音视频信令和已签名服务端消息不调用文本审核。
  • 空文本直接放行。

4.5 阿里云结果处置

IM Server 统一映射为:

内部结果 动作
allow 继续加密、入库和广播
block 不入库、不广播,返回统一业务错误
audit_only 允许发送,但写入审核命中日志
retryable_error 根据 failure_mode 放行或拦截

不只依赖 RiskLevel/Label 一个字段,需保留与验证阿里云原始结果中的扩展判断,针对引流、诈骗等样本建立回归测试集。

4.6 超时与降级

  • 阿里云请求不得无限等待。
  • 建议默认 failure_mode=allow,阿里云异常时允许发送并记录告警,防止第三方故障造成 IM 全局不可用。
  • 连续失败达到阈值后进入短时熔断;熔断期间按失败策略处理。
  • 阿里云调用不得在数据库事务中执行。

5. 消息加密设计

5.1 算法

  • 算法:AES-256-GCM。
  • DEK 长度:32 字节。
  • Nonce:每次加密使用 crypto/rand 生成的独立 12 字节随机值。
  • GCM 标签:使用 Go 标准库默认 16 字节认证标签。
  • 明文编码:将原 content JSON 规范化序列化为 UTF-8 字节。
  • 不允许固定 Nonce,不允许 AES-ECB,不使用未认证的 AES-CBC/CTR。

5.2 AAD

每条消息加密时绑定以下 AAD:

tenant_id | topic | seqid | from | client_msg_id | key_version | content

content 是字面标识,用于与未来可能加密的 head 区分。AAD 按固定字段顺序和长度前缀编码,禁止使用可产生拼接歧义的简单字符串。

5.3 密钥层级

HotGo字典根密钥(KEK)
    -> 加密每个商户、每个版本的DEK
        -> DEK加密该商户消息

根密钥使用 HotGo hg_sys_dict_data 保存,但必须满足:

  1. 使用独立的内部字典类型,不出现在通用字典管理页。
  2. 后端字典列表、选项和导出接口必须对该类型做服务端拒绝,不能仅依赖菜单隐藏。
  3. 根密钥为 32 字节强随机值的 Base64 编码,不使用口令、UUID 或可读文本。
  4. 根密钥只由 Admin Server 密钥服务读取,不通过通用配置接口返回。
  5. 根密钥不写日志,不在后台页面回显,不进入 Kafka 消息。

5.4 商户密钥管理表

在 HotGo 库新增 im_message_encryption_keys:

字段 类型 说明
id bigint 主键
tenant_id bigint 商户 ID
key_version int 密钥版本,从 1 递增
algorithm varchar(32) 固定 AES-256-GCM
encrypted_dek blob KEK 加密后的 DEK
dek_nonce varbinary(12) 加密 DEK 时的随机 Nonce
status varchar(16) pending / active / retired / disabled
created_at datetime 创建时间
activated_at datetime 启用时间
retired_at datetime 退役时间
created_by bigint 操作人
remark varchar(255) 备注

约束:

  • 唯一键:tenant_id + key_version。
  • 每个商户同时只能有一把 active 密钥。
  • retired 密钥仍可用于解密历史消息,不得再用于新消息加密。
  • disabled 只用于安全事故应急,禁用前必须完成历史消息转密钥。

5.5 IM 消息表改造

im_messages 和 im_message_revisions 新增:

字段 类型 说明
content_ciphertext longblob 消息密文,包含 GCM 认证标签
content_nonce varbinary(12) 本条消息随机 Nonce
key_version int 商户 DEK 版本
encryption_alg varchar(32) AES-256-GCM
encrypted tinyint 0 明文兼容,1 密文

迁移完成前保留现有 content JSON,规则如下:

  • 新写入:content=NULL、encrypted=1,只保存密文。
  • 旧数据:content IS NOT NULL、encrypted=0,按原逻辑读取。
  • 解密成功后还原为原 any JSON 结构,不修改对客户端的协议。
  • head 第一阶段不加密,但禁止在 head 中放置消息正文、联系方式等敏感明文。

im_group_messages 是群消息派生查询表,不得另存一份明文。实现时二选一:

  1. 推荐:仅保存索引、状态和统计字段,通过 topic + seqid 关联 im_messages 解密。
  2. 兼容:同步增加密文字段,但加密输入和密文必须与主消息表保持一致。

不允许 im_messages 已加密而 im_group_messages.content 仍保留明文。

5.6 加密和解密时机

新消息

校验消息
  -> 提取审核文本
  -> 内容审核
  -> 分配seqid/组装AAD
  -> 序列化content
  -> 使用当前active DEK加密
  -> 事务写入密文和消息元数据
  -> 向在线订阅者广播原内存明文

历史消息和离线同步

从数据库读取密文
  -> 按tenant_id + key_version获取DEK
  -> 重建AAD
  -> GCM验签并解密
  -> JSON反序列化
  -> 按原Tinode协议返回

任何验签失败均必须当作数据篡改或密钥错误,禁止返回部分内容或尝试忽略错误。

5.7 密钥获取和缓存

  • IM Server 不得每发送一条消息都跨境请求 HotGo。
  • IM Server 按 tenant_id + key_version 在内存中缓存已解密 DEK。
  • 新消息使用当前 active 密钥,历史消息按记录中的 key_version 取密钥。
  • 密钥变更发布 message.encryption.key.updated 配置事件,IM Server 按商户刷新。
  • HotGo 短时不可用时继续使用内存密钥。
  • IM Server 重启后如无法取得当前密钥,该商户禁止写入新消息,不得降级为明文落库。

5.8 密钥轮换

  1. Admin Server 生成新 DEK,使用 KEK 加密后以 pending 状态入库。
  2. 通知 IM Server 拉取并验证新密钥。
  3. 验证成功后,在事务中将旧密钥设为 retired,新密钥设为 active。
  4. 新消息立即使用新版本,旧消息仍按原版本解密。
  5. 历史转密钥作为单独低速任务执行,不阻塞即时通信。

6. Admin Server 改造

6.1 内容审核页面

将现有“审核插件”配置补齐为可用功能:

  • 阿里云基础配置。
  • 真实连通性检查。
  • 商户审核开关和场景开关。
  • 风险等级与处置动作映射。
  • 异常降级策略。
  • 配置发布版本和生效时间。

非默认商户显示“未开启内容审核”,不显示阿里云密钥编辑项。

6.2 消息加密管理页面

增加平台级“消息加密”页面,只允许最高权限平台管理员使用:

  • 查看商户加密状态、当前密钥版本和最后轮换时间。
  • 初始化商户密钥。
  • 启用/暂停商户消息加密。
  • 轮换密钥。
  • 查看历史迁移进度。
  • 密钥明文、根密钥和 encrypted_dek 均不展示。

6.3 内部接口

建议增加:

方法 路径 用途
GET /internal/im-config/tenants/{code}/moderation-policy IM Server 拉取商户审核策略
GET /internal/im-config/moderation/provider IM Server 拉取阿里云配置
GET /internal/im-config/tenants/{code}/message-keys/active 获取当前消息 DEK
GET /internal/im-config/tenants/{code}/message-keys/{version} 获取指定版本 DEK
POST /internal/im-config/tenants/{code}/message-keys/ack IM Server 确认新密钥可用

密钥接口返回的是服务端使用的 DEK,不允许从浏览器或通用 Admin API 调用。接口必须校验服务身份、时间窗、签名、请求 ID 和重放。

6.4 后台消息查询

  • Admin Server 继续调用 IM Server internal admin-query,不直连 IM 业务库解密。
  • IM Server 在商户数据权限通过后批量解密当页消息。
  • 每次查看明文记录管理员 ID、商户、IP、查询条件、返回数量、时间和 TraceID。
  • 列表只解密当页,禁止一次加载全部历史消息。

7. IM Server 改造

7.1 新增内部模块

server/moderation/
  provider.go              审核Provider接口
  policy.go                商户策略和缓存
  text.go                  文本提取与结果映射
  aliyun/text.go           TextModerationPlus实现

server/messagecrypto/
  cipher.go                AES-256-GCM加解密
  key_provider.go          密钥获取接口
  key_cache.go             tenant/version内存缓存
  serializer.go            content规范序列化

不将加密和阿里云调用直接写进 MySQL adapter;adapter 只保存已加密的存储模型和解密所需元数据。

7.2 消息发送接入点

在 Topic.handlePubBroadcast 中,于 saveAndBroadcastMessage 之前执行:

  1. 过滤 WebRTC/系统信令等不审核内容。
  2. 提取文本。
  3. 根据商户策略执行审核。
  4. 拦截时通过 Tinode ctrl 回复业务错误,不进入保存和广播。
  5. 放行时进入加密保存。

进程内广播使用本次请求已有明文,不为广播执行“加密后立即解密”。

7.3 资料修改接入点

用户昵称/签名、群名称/公告、群昵称的 HTTP 和 Tinode meta 修改通道都必须接入同一 moderation service,不能只修改某一个 HTTP API 而留下协议绕过路径。

7.4 存储层改造

  • types.Message 和 types.MessageRevision 增加密文及密钥元数据字段。
  • MySQL 的普通库和共享库/多商户 adapter 必须同时改造。
  • 新建库 schema、启动 ensure 迁移和手工 SQL 脚本必须保持一致。
  • Save、GetOne、GetAll、编辑、撤回、删除、历史修订、管理后台查询均需覆盖。
  • 解密层应在业务存储服务边界统一执行,避免上层遗漏。

7.5 缓存与多 Pod

  • 每个 Pod 保持本地密钥和审核策略缓存。
  • Kafka 配置事件使所有 Pod 同时失效。
  • 事件处理必须幂等,重复消息不得导致重复创建密钥。
  • 新旧密钥切换期间,所有 Pod 必须同时具备旧版本解密和新版本加密能力。

8. 审核和解密日志

在 IM 业务库增加 im_content_moderation_logs:

字段 说明
id 主键
request_id 审核请求 ID
scene 昵称、单聊、群聊等
topic / seqid 关联消息,拦截时 seqid 可为空
actor_id 内容提交人
provider local / aliyun_green
provider_request_id 第三方 RequestID
risk_level 统一风险等级
risk_labels 命中标签 JSON
action allow / block / audit_only
content_hash SHA-256 摘要,不保存明文
latency_ms 审核耗时
error_code 脱敏错误码
created_at 创建时间

后台明文查看继续写入统一审计事件,必须区分“列表解密”、“详情解密”和“导出解密”。默认不提供明文批量导出;需要开放时必须增加独立权限和审批。

9. 关键词查询限制

消息加密后,MySQL 无法对密文执行原有 LIKE '%keyword%'。第一阶段采用:

  1. 后台查询必须先提供商户、时间范围,并尽量提供用户或群号。
  2. IM Server 先用明文元数据缩小候选集。
  3. 对有上限的候选集批量解密,再在内存中匹配关键词。
  4. 限制查询最大时间范围和候选消息数,超限返回明确提示。

不建议在第一阶段建设明文分词索引,否则会降低加密保护效果。若后续必须支持全库高性能检索,再单独评审 HMAC 盲索引方案。

10. 历史数据迁移

10.1 发布顺序

  1. 发布 HotGo 表结构、根密钥和密钥管理能力。
  2. 为默认商户初始化第 1 版 DEK,但不开启加密。
  3. 发布 IM Server 双读兼容版本,验证密钥获取、加解密和内容审核。
  4. 开启新消息加密写入,保持旧数据明文可读。
  5. 启动历史数据分批迁移。
  6. 对比数量、抽样解密和 GCM 验签,完成后清空明文 content。
  7. 观察期结束后,再决定是否删除原明文字段。

10.2 迁移任务要求

  • 按主键或 topic + seqid 分批,不长时锁表。
  • 每批使用独立事务,记录游标、成功数、失败数和最后错误。
  • 重复执行必须幂等,encrypted=1 的记录直接跳过。
  • 先写入密文并验证可解密,再将明文字段置空。
  • 失败记录保留明文并进入重试列表,不得丢失原数据。
  • 迁移速率可配置,并监控数据库负载和消息延迟。

11. 错误码和客户端兼容

客户端不参与加解密,不修改消息协议。新增业务错误建议:

错误 说明 客户端处理
CONTENT_REJECTED 内容审核拒绝 保留未发送文本并提示修改
CONTENT_REVIEW_UNAVAILABLE 审核不可用且策略要求拦截 提示稍后重试
MESSAGE_ENCRYPTION_UNAVAILABLE 未取得加密密钥 提示服务暂时不可用
MESSAGE_DECRYPT_FAILED 历史消息验签/解密失败 展示“消息暂时无法加载”,不展示乱码

不向客户端返回阿里云原始错误、风险标签详情、密钥版本内部信息或解密堆栈。

12. 监控指标

12.1 内容审核

  • 按商户/场景统计请求量、放行数、拦截数、降级数。
  • 阿里云耗时 P50/P95/P99。
  • 超时率、错误率、熔断状态。
  • 各风险等级和标签命中数。

12.2 消息加密

  • 加密/解密成功数、失败数和耗时。
  • 各商户当前密钥版本。
  • 密钥获取成功率、缓存命中率和缓存最后刷新时间。
  • 明文历史记录剩余数、迁移速率和迁移失败数。
  • GCM 验签失败必须产生高优先级告警。

监控标签不得使用消息正文、密文、Nonce、DEK 或 KEK。

13. 验收标准

13.1 审核验收

  • default 商户开启后,单聊、群聊、用户昵称、群名称和群昵称均实际调用对应阿里云 Service。
  • 其他商户关闭时不调用阿里云。
  • 拦截内容不进入 im_messages 和 im_group_messages,也不向订阅者广播。
  • 本地敏感词与阿里云开关可独立启停。
  • 阿里云超时严格按 failure_mode 处理。
  • 配置发布后所有 IM Pod 在规定时间内生效。

13.2 加密验收

  • 开启商户的新消息在数据库中不存在可读正文。
  • 相同内容连续发送两次,Nonce 和密文必须不同。
  • 修改密文、Nonce、AAD 关联字段的任意一处后,解密必须失败。
  • 在线发送、离线同步、多端漫游、编辑、回复、撤回、删除保持原有业务行为。
  • Admin 有权限账号可查看解密明文,无权限账号被拒绝。
  • 查看明文必须形成完整审计记录。
  • 旧密钥退役后仍可解密旧版本消息,新消息只使用新版本。
  • HotGo 暂时断开时,已有内存密钥的 IM Pod 仍可工作;无密钥时绝不允许明文降级落库。

13.3 性能验收

  • 纯 AES 加密不得成为消息链路主要瓶颈。
  • 密钥缓存命中时不发生跨境 HotGo 请求。
  • 审核关闭的商户不增加阿里云网络耗时。
  • 审核开启时应分别统计本地处理、阿里云调用和消息入库耗时。
  • 历史迁移期间即时消息发送和数据库负载不得超过上线阈值。

14. 开发分批建议

批次一:配置和密钥基础

  • Admin 阿里云内容审核配置、商户策略、密钥表和密钥管理页。
  • 字典根密钥隔离和专用密钥服务。
  • Admin internal config/key API 及 Kafka 失效事件。

批次二:IM 文本审核

  • 阿里云 TextModerationPlus Go 客户端。
  • 策略缓存、文本提取、消息和资料修改接入。
  • 审核日志、错误码、告警和回归测试。

批次三:新消息加密

  • AES-256-GCM 模块、密钥缓存和存储模型。
  • 新消息加密写入和历史消息解密读取。
  • 单聊、群聊、修订、后台查询的一致性验证。

批次四:历史迁移和轮换

  • 历史明文分批加密。
  • 迁移仪表盘、少量失败重试和数据对账。
  • 密钥轮换和历史转密钥测试。
  • 完成观察后清理明文。

15. 开发前必须确认的决策

决策 文档默认值
阿里云异常时放行还是拦截 放行并告警
是否审核系统消息 不审核
是否加密 head 第一阶段不加密
第一阶段启用商户 仅 code=default
是否允许无密钥时明文降级 不允许
后台是否允许明文导出 默认不允许
历史消息是否迁移 是,分批迁移
根密钥位置 HotGo 内部字典数据,严禁通用接口暴露

16. 现有实现对照

能力 现状 本次改造
阿里云文本审核 文档已人工验证,IM Server 无 Go 调用实现 增加 TextModerationPlus 客户端和业务接入
本地敏感词 Admin 已有 CRUD,IM Server 未消费 作为可选补充审核
审核配置 im_external_config 已有 moderation 占位 增加真实检查、商户策略和发布
配置同步 已有 internal config 客户端、Kafka 配置事件和缓存失效 复用并扩展新的策略/密钥类型
消息存储 im_messages.content 和修订表 content 为 JSON 明文 改为 AES-256-GCM 密文且双读迁移
群消息派生表 im_group_messages.content 为 JSON 明文 去明文副本或同步密文化
后台消息查询 IM internal-query 返回库内内容 增加权限校验、解密和查看审计