Skip to content

ADR-005 本地加密存储

状态

提案,待总控决策。本文定义隔离与失败语义,不认定任何数据库或密钥封装组合已经审计或适合生产。

背景

客户端需要保存身份、联系人验证状态、协议会话、消息、草稿、Outbox、设置和恢复状态。数据既要支持事务、增量查询、迁移和崩溃恢复,也必须满足 Profile、真实空间与伪装空间的强隔离。

已验证的仓库事实

截至 2026-09-23,当前仓库没有 iOS 工程、数据库 schema 或依赖锁定文件;GRDB、SQLCipher、Keychain 和 Secure Enclave 组合尚未完成构建、许可、性能、迁移或真机验证。

说明书 MUST

  • 不得使用未加密 SwiftData 保存消息。
  • 本地数据库密钥存入 Keychain,并由 Secure Enclave 保护的密钥封装;设备锁定后数据库不可读取。
  • 每个服务器 Profile 使用独立数据库、Keychain 命名空间、身份和通知路由。
  • Decoy 使用独立容器,不挂载或复用真实数据。
  • 主/假密码 KDF 输出只用于验证和解封随机数据主密钥,不直接作为数据库密钥。
  • 迁移失败不得删除原始数据库。
  • 格式化优先删除数据库密钥与身份/设备私钥封装材料,再处理数据库、WAL/SHM 和其他残留。

建议决策

优先验证“GRDB + SQLCipher + iOS Data Protection + Keychain 中的封装材料 + Secure Enclave 背书的解封密钥”方案。每个真实 Profile 和 Decoy 分别使用独立数据库文件、随机数据库密钥、Keychain service/account 命名空间和附件目录。

该建议的成立条件是:依赖许可兼容、SQLCipher 构建与更新路径可维护、Swift 6 并发边界可证明、锁定态和 WAL/SHM 行为符合要求、独立安全评审通过。任一条件不满足时回到方案比较,不以临时明文存储兜底。

选项比较

选项 A GRDB 加 SQLCipher

  • 优点:SQLite 事务与迁移成熟;可明确控制连接、WAL、查询和每 Profile 文件;适合 Outbox 原子写入。
  • 缺点:需要维护 SQLCipher 构建、密钥注入、迁移和许可证/SBOM;扩展与备份时容易误复制 WAL/SHM。
  • 待验证:iOS 26、Swift 6、目标设备性能、锁定态访问、崩溃恢复、依赖供应链。

选项 B 其他经过审计的加密数据库

  • 优点:可能降低集成或运维成本。
  • 缺点:必须证明逐 Profile 文件隔离、事务、分页、全文搜索、迁移回滚、密钥轮换和取证残留能力。
  • 待验证:产品成熟度、许可、导出/恢复格式、故障可观测性。

不采用 未加密数据库加字段级零散加密

原因:schema、索引、草稿、状态字段、临时表和日志容易泄露;无法自然满足数据库整体锁定与可靠迁移。字段级加密只可作为额外分层,不替代加密数据库。

存储布局

建议使用随机内部标识,不从 VMax 地址、实例域或联系人信息派生路径:

text
Application Support/
  spaces/
    real/<opaque-profile-id>/
      store.sqlite
      store.sqlite-wal
      store.sqlite-shm
      attachments/
      cache/
      migration/
    decoy/<opaque-decoy-id>/
      store.sqlite
      ...
  • Profile registry 只保存打开选择所需的最小不透明索引,不保存显示名、完整地址、服务器域或密钥。
  • 不把多个 Profile 放进同一数据库后仅依赖行级过滤。
  • 文件使用适当 Data Protection class;具体 class 必须通过后台通知、设备锁定和可用性测试后决定。
  • 临时导出、迁移副本和恢复暂存使用同等或更严格保护,并有明确清理责任。

密钥层级建议

text
用户口令 --内存困难 KDF--> KEK/验证材料

                               └─ 解封随机 App Data Master Key

Secure Enclave 背书的设备绑定密钥

                               └─ 保护 Keychain 中的 Profile 封装材料

每 Profile 随机 Database Key ──> SQLCipher 数据库
每身份/设备私钥              ──> 独立封装对象

这里的箭头描述职责,不冻结具体算法、KDF 参数或封装格式。Secure Enclave 通常不直接保存任意对称密钥,实际应使用经验证的平台封装路径;API 能力见 ADR-002。设备绑定封装不得写入可移植备份。

Keychain 隔离

  • service/account 由版本化中性前缀、空间类别和随机 Profile ID 组成,不包含 realfake_password、地址或域名等可暴露语义。
  • 每个 Profile 的数据库密钥、身份私钥封装、设备私钥封装和 Push 路由凭据分项保存,以便精确轮换和擦除。
  • 访问控制和 accessibility 需要真机验证;目标是设备锁定时无法解封真实数据库。
  • 读取 API 返回短生命周期敏感容器,禁止 Data 被日志、崩溃报告或通用 Codable 路径复制。
  • 删除 Profile 后枚举并核对所有预期项;禁止用宽泛前缀误删其他 Profile。

数据库边界

建议单库内至少区分:领域记录、协议不透明状态、Outbox、迁移元数据和本地设置。敏感搜索索引属于 S2 内容,必须留在加密库内。

Outbox 消息记录、不可变 envelope 和幂等键应在同一事务提交。网络层只读取待提交 envelope,不获得数据库密钥或其他会话状态。Crypto actor 生成 envelope 与推进协议状态的原子性必须与最终协议实现共同设计,不能由数据库 adapter 猜测。

服务端已确定使用 PostgreSQL 和 Redis/队列,但它们位于客户端信任边界之外。Redis/服务端队列只负责服务端短期异步工作,不能作为 iOS Outbox 的唯一状态来源,也不能参与本地 Profile 隔离或恢复判定。iOS 不直连 PostgreSQL 或 Redis。

打开与关闭

  1. AppShell 保持锁定,不预开真实库。
  2. AppUnlock 只返回抽象空间结果。
  3. 真实解锁成功后,KeyProtection 解封目标 Profile 所需材料。
  4. DatabaseFactory 校验文件、schema 版本和迁移前置条件后打开单个 Profile。
  5. ProfileSession 启动网络、Outbox 和密码 actor。
  6. 切换、锁定或后台策略要求关闭时,先停止新操作和任务,再检查点、清理内存并关闭连接。

任何阶段失败均保持锁定或进入恢复态,不创建同路径空白数据库,不自动删除“损坏”文件。

迁移与密钥轮换

  • 迁移先验证可用空间、版本跨度和备份/快照策略;使用事务或影子文件,失败保留原库。
  • 不允许未知 schema 静默打开或降级写入。
  • 密钥轮换采用新文件/受控重加密还是 SQLCipher 原地 rekey,需以崩溃恢复和取证测试决定。
  • 更新中断后必须可判定“旧库完整”或“新库完整”,不能选中半迁移文件。
  • 每个 Profile 独立迁移;一个 Profile 失败不得打开另一个 Profile 的数据作为替代。

删除与格式化边界

  • 普通删除 Profile 与安全格式化使用不同命令和授权路径,但共享精确资源清单与幂等删除 primitive。
  • 格式化提交点优先是删除可解封真实数据库和私钥的关键封装材料;随后清理文件残留。
  • WAL/SHM、附件、草稿、缓存、临时文件、迁移副本、诊断日志和本地通知必须列入清单。
  • Files、iCloud Drive、AirDrop 或其他 App 外部 .vmaxbak 不在本地格式化范围。
  • 原子性、中断恢复和用户可见语义由 ADR-011 决定;本 ADR 不自行冻结。

验证计划

  • 真机锁定:打开库后锁屏、冷启动锁屏、重启未首次解锁、后台唤醒,验证不可读边界。
  • 文件取证:正常使用、崩溃、迁移、rekey、删除后检查数据库、WAL/SHM、临时文件和备份副本中的明文与密钥。
  • Profile 隔离:相同业务 ID、多 Profile 并发切换、错误路径注入、Keychain 枚举与删除范围检查。
  • 故障注入:磁盘满、I/O 错误、进程强杀、schema 损坏、旧版本数据库和未知版本。
  • 并发:Swift 6 数据竞争检查、actor 取消、连接泄漏与关闭超时。
  • 性能:说明书的 10 万条单会话、100 万条总库、增量加载和本地搜索基线。
  • 供应链:依赖哈希、SBOM、许可证、漏洞扫描、可重复构建和更新演练。

待总控决策

  • 是否采用选项 A;推荐先做两周内可丢弃原型,再决定依赖冻结。
  • SQLCipher/GRDB 的具体版本、集成方式和许可证接受;需许可与供应链评审。
  • Keychain accessibility、Data Protection class 和 Secure Enclave 封装 API;需真机矩阵验证。
  • 数据库密钥轮换采用影子库还是原地 rekey;需强杀与取证结果选择。
  • 是否在首版启用本地全文搜索;若启用,索引容量与泄露面需单独测试。
  • 双密码 KDF、槽位封装和格式化提交点由 ADR-011 决定,本文不冻结。

系统初步设计 · 待决事项不代表批准 · 安全方案尚未完成独立审计