外观
双密码与伪装空间设计
1. 产品边界
主密码和假密码是本地 App Lock 凭据,不参与服务端认证。主密码进入真实 Profile;假密码从同一入口进入独立空白 Decoy Profile。假密码触发本地格式化是可选能力,默认关闭。
本设计降低旁观或胁迫解锁时直接暴露真实内容的风险,但不承诺对设备取证、操作系统失陷、长期行为观察或外部备份检查提供完全可否认性。
2. 来源分类
v1.1 MUST
- 主/假密码互不相同,只在本地使用,不进入网络、日志、崩溃、分析或诊断。
- 两个密码使用独立盐和内存困难 KDF;KDF 输出仅用于验证/解封随机密钥。
- 同一输入界面路由
real / decoy / invalid;错误提示中性。 - Decoy 使用独立容器,默认无真实身份、联系人、会话、服务器和备份痕迹。
- 格式化默认关闭;启用需主密码、不可逆警告和二次确认。
- 只有精确假密码验证成功可触发格式化;普通错误、Face ID 失败、取消、退后台或系统终止不得触发。
说明书建议
- 默认至少 8 字符;纯数字 PIN 至少 6 位;具体口令策略和 KDF 参数需可用性、设备性能和审计共同决定。
- 使用中性槽位名减少静态检查直接暴露语义,但这不构成取证不可区分证明。
当前已验证事实
- 当前仓库没有 AppUnlock、Decoy Profile、通知或格式化实现;本文定义的是待实现/验证的状态机。
3. 安全目标与非目标
目标:
- 未解锁前,UI 和业务层无法知道用户将进入 Real 还是 Decoy。
- Decoy 运行期间不挂载、不查询、不通知、不导出 Real 数据。
- 两种有效密码的验证路径尽量相近,不通过错误文案或明显时序暴露槽位。
- 格式化关闭时,无论进入 Decoy 多少次,Real 密钥和数据均不变化。
非目标:
- 隐藏 App 已安装、网络访问或外部加密备份的存在。
- 抵抗已完全控制 iOS、键盘、屏幕录制或进程内存的攻击者。
- 保证两条路径在所有硬件和系统状态下严格恒定时间。
- 伪造真实社交历史或承诺“可信否认”;MVP Decoy 是自然空白空间。
4. 解锁状态机
| 当前状态 | 事件 | 条件 | 动作 | 新状态 |
|---|---|---|---|---|
| Locked | 密码提交 | 主槽验证成功 | 解封 Real 主密钥,初始化 Real 容器 | RealUnlocked |
| Locked | 密码提交 | 假槽成功,格式化关闭 | 仅初始化 Decoy 容器 | DecoyUnlocked |
| Locked | 密码提交 | 假槽成功,格式化开启 | 进入中性维护/销毁协调器;不初始化 Real;完成或继续清理后打开 Decoy | DecoyUnlocked |
| Locked | 密码提交 | 两槽均失败 | 增加本地延迟,不访问任何 Profile | Locked |
| Locked | Face ID 成功 | 用户允许快捷解锁 | 只可解锁 Real;不得自动触发 Decoy/格式化 | RealUnlocked |
| 任意锁定流程 | 取消/退后台/系统终止 | 任意 | 不调用格式化入口;清理输入 | Locked |
| RealUnlocked/DecoyUnlocked | 超时/主动锁定 | 任意 | 释放对应容器和内存会话 | Locked |
不允许“连续输错 N 次清空”。SecureEraseCoordinator 只能接收不可伪造的本地枚举结果 decoyAuthenticatedAndEraseEnabled,不能接收用户输入字符串或布尔组合。
5. 凭据记录候选
每个槽位至少包含:随机盐、KDF 算法/版本/参数、口令验证材料或密钥封装、创建版本。持久化键名使用中性编号;记录长度和访问控制尽量一致。
待总控决策 01 验证与解封结构
选项 A 两个槽位各自持有认证封装:主槽封装 Real 主密钥,假槽封装 Decoy 主密钥/格式化授权材料。
- 优点:成功路径自然得到对应能力,不需要保存独立可比较 verifier。
- 风险:封装格式或密钥存在差异可能泄露槽位;假槽不能获得 Real 主密钥。
选项 B KDF verifier 后再访问独立 Keychain 项:先判定槽位,再读取对应密钥。
- 优点:实现直观。
- 风险:显式 verifier、Keychain 访问模式和分支更容易暴露;错误 oracle 面更大。
推荐:优先验证 A 的平台可行性;槽位记录固定长度、同版本、相近工作量,假槽绝不包含可解封 Real 的材料。最终需 iOS 安全评审。
KDF 要求
- Argon2id 或经审计确认的等价内存困难 KDF;参数按最低支持设备校准并设格式版本。
- 两槽独立盐;域分离包含 App、槽位用途和格式版本。
- 同一次提交对两个候选槽执行等价工作,避免“找到第一个即返回”的明显时序差异;实际侧信道仍需真机统计。
- 参数解析有内存/时间上限,防止损坏记录造成拒绝服务。
- 输入使用可控字节缓冲并尽快释放;不得写入自动填充、键盘学习或分析事件。
6. Decoy 数据边界
Decoy 必须有独立:
- 数据库文件、WAL/SHM、附件、草稿、缓存和临时目录;
- Keychain service/account 或 access group 内命名空间;
- 依赖容器、路由、搜索索引、分享数据和诊断记录;
- 通知显示状态和 badge 状态。
进入 Decoy 时不得:
- 打开 Real 数据库来判断是否有内容;
- 初始化 Real
CryptoSessionActor、WebSocket、Push 映射或服务器 Profile; - 从 Real 复制联系人、头像、最近服务器或备份提示;
- 记录“假密码命中”“格式化触发”等可识别日志。
默认界面表现为自然的首次使用/尚未创建身份状态。是否允许用户在 Decoy 中创建持久假身份属于后续产品范围,MVP 不预设。
7. 通知 后台与系统集成
- App 进入后台立即使用与 Profile 无关的遮蔽画面。
- 进入 Decoy 后清除当前可见 VMax 通知和 badge;Notification Service Extension 不得读取 Real 数据库生成摘要。
- Decoy 运行时收到唤醒,只能静默标记“有待处理工作”,不能展示真实联系人或摘要;返回 Locked/Real 后再拉取处理。
- Spotlight、Siri、Widget、Live Activity、分享扩展、快捷方式和系统搜索若不能证明按 Profile 隔离,MVP 默认不集成或不索引敏感数据。
- Face ID 只能是 Real 的可选快捷入口;锁屏必须提供直接输入密码的路径。
具体 iOS 生命周期和通知接口交给 iOS/Push 任务实现。
8. 可区分侧信道与产品披露
| 侧信道 | 风险 | 缓解 | 剩余边界 |
|---|---|---|---|
| 解锁耗时 | 主/假/错误路径可被统计区分 | 等价 KDF 工作、统一动画和错误 | 系统负载和后续初始化仍可能不同 |
| 磁盘容量/文件数 | Real 数据量暴露 | 文件保护、命名中性、Decoy 独立 | 设备取证仍可能看出差异 |
| Keychain 项 | 槽位语义暴露 | 中性键名、相同结构和访问控制 | 高权限取证不在保证范围 |
| 通知与网络 | Decoy 时仍有真实活动 | 无内容唤醒、清摘要、不加载 Real | 时间相关性仍可能被观察 |
| 外部备份 | .vmaxbak 暴露真实身份存在 | 加密、中性元数据和用户提示 | App 无法删除外部副本 |
| 错误/日志 | 直接记录槽位或格式化 | 字段白名单和中性错误 | 系统级日志需真机审计 |
设置和帮助文案应说明这是“伪装空间与本地数据保护”,而非不可被取证识别的保证。
9. 测试矩阵
- 主正确、假正确、错误、空输入、取消、Face ID 成功/失败、退后台、kill/relaunch。
- 格式化开/关各自执行上述矩阵;关闭时假密码 100 次不能改变 Real。
- 两个以上服务器 Profile,检查数据库、Keychain、通知、缓存、搜索和剪贴板交叉访问。
- Decoy 冷启动、后台唤醒、系统通知、分享扩展和诊断导出均不得出现真实数据。
- 真机统计主/假/错误验证时延分布;把可稳定分类的差异列为安全问题,不伪称恒定时间。
- 持久化结构静态检查不应出现
real_password、fake_password等语义名,但不得把通过此检查当作可否认性证明。
10. 独立审计问题
- KDF/封装结构是否让假槽获得任何 Real 解封能力?
- 同一次输入评估两个槽是否引入 DoS、时序或缓存侧信道?
- Decoy 是否通过 SwiftUI 环境、单例、共享数据库连接、通知扩展或系统索引意外挂载 Real?
- 格式化授权是否可能由伪造状态、损坏记录或迁移错误触发?
- 进入 Decoy 前清通知是否本身形成明显信号;产品如何准确披露剩余风险?