1. 敏感数据分类与存储边界
在移动端组件内,密码、、密钥、会话ID 等数据一旦泄露,可直接导致用户身份冒用、数据窃取或服务端信任链被攻破。因此,第一步必须清晰识别哪些数据是“敏感”的,并明确其存储边界:是否需要持久化、存活多久、何时清除。
1.1 敏感数据类型全览
下表列出组件内最常见的四类敏感数据及其典型特征:
| 数据类别 | 典型示例 | 存储边界要求 | 清除要点 |
|---|---|---|---|
| 密码 | 用户登录密码、支付密码 | 不应明文持久化;仅用于一次性认证后丢弃 | 退出登录或密码修改时立即从内存和存储中擦除 |
| JWT、OAuth 2.0 access_、refresh_ | 短期 可内存缓存;长期 需加密存储 | 过期或用户登出时立即失效并删除 | |
| 密钥 | 对称加密密钥、API 签名私钥、证书私钥 | 必须使用专用安全硬件(如 Keychain/Keystore)持久化 | 密钥轮换时删除旧版本,应用卸载时随安全域删除 |
| 会话ID | Session ID、CSRF | 通常为内存态,不落盘;如持久化需绑定设备环境 | 会话超时或用户主动退出时立即销毁 |
1.2 持久化 vs 非持久化数据
区分数据的持久化需求是设计存储策略的核心。错误的持久化判断是数据泄露的主要根源。
| 维度 | 非持久化数据 | 持久化数据 |
|---|---|---|
| 存储位置 | 内存、进程堆栈、寄存器 | Keychain/Keystore、加密文件、沙箱数据库 |
| 生命周期 | 随组件销毁或进程结束自动释放 | 主动创建,需显式清除或过期策略 |
| 清除频率 | 无需手动清除(GC 或进程回收) | 每次认证状态变更、版本升级、卸载时必须检查 |
| 泄露风险 | 低(攻击时间窗口短) | 高(攻击者可能获得物理镜像) |
| 典型数据 | 一次性密码、内存中的会话对象 | 长期 refresh_、设备密钥 |
关键原则: 仅当数据跨组件重启或应用重启后仍必须使用时,才考虑持久化;否则一律使用非持久化内存存储。
1.3 生命周期与清除策略
每一类敏感数据都必须拥有明确的 创建 — 使用 — 清除 生命周期,并由代码强制实施。生命周期管理包含三个阶段:
- 创建阶段: 从网络响应或本地认证结果中提取敏感数据,立即存入安全容器(如 Keychain item 或内存 SecureString),不得在日志、全局变量或 UserDefaults 中明文滞留。
- 使用阶段: 通过指针或安全句柄访问,避免多次复制;使用完毕立即调用清除方法(如
SecItemDelete、Array.Clear、SecureString.Dispose)。 - 清除阶段: 触发清除的事件包括:用户登出、 过期、应用进入后台(选择性清除)、应用卸载、密钥轮换。清除必须同步执行并覆盖原数据。
调试提示: 在开发阶段启用敏感数据审计日志(仅限 Debug 构建),可以定期输出所有未正确清除的敏感数据对象,帮助定位遗漏的清除点。
1.4 数据分类与生命周期框架图
1.5 实施检查清单
- ✅ 已枚举组件内所有敏感数据,并标记其类别(密码//密钥/会话ID)。
- ✅ 为每类数据决策了持久化必要性:需要跨进程/跨重启?→ Keychain;否则→内存。
- ✅ 已在代码中为每条敏感数据定义清晰的清除触发器(登出、过期、卸载等)。
- ✅ 确认不使用
UserDefaults、NSUserDefaults、SharedPreferences存储敏感数据。 - ✅ 在单元测试中增加了敏感数据生命周期验证(写后读、清除后读断言为空)。
常见错误: 将 refresh_ 存入 UserDefaults 并用 plist 持久化,导致应用备份或日志抓取时泄露。记住——持久化不等于 UserDefaults,安全持久化必须使用专用安全存储 API。