本节摘要:M1 的根因几乎总在「不该存的全存了 + 存了也不加密」。SOURCE 3.3 要求用 Android Keystore / iOS Keychain 管密钥、限制数据生命周期、安全擦除。本节含 EncryptedSharedPreferences 与 Keychain 属性对照。
Accessible 级别allowBackup=false| 数据 | 建议 | 禁止 |
|---|---|---|
| Access Token | 加密 + 短 TTL | 明文 SharedPreferences |
| 密码 | 不存(用 Token) | 任何形式本地存密码 |
| 生物模板 | 系统 API 管 | 自建指纹文件 |
| 缓存图片 | 沙箱内、非敏感 | SD 卡明文 |
val masterKey = MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() // 密钥在 Android Keystore,prefs 文件本身加密
Keystore 要点(SOURCE 3.3):
setUserAuthenticationRequired(true) 绑定生物识别KeyInfo.isInsideSecureHardware 检测Manifest:
<application android:allowBackup="false" ...>
let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAttrAccount as String: "access_token", kSecValueData as String: tokenData ] SecItemAdd(query as CFDictionary, nil)
WhenUnlockedThisDeviceOnly:不解锁不可读、不 iCloud 同步、不换机迁移 — 适合会话类 secret。

clear() prefs + SecItemDelete + 内存 zeroizedelete(),支付类 App 禁写外部存储| 检查 | 命令/方法 |
|---|---|
| 备份面 | adb backup 应失败或空 |
| 明文 | MobSF 搜 password、token 字符串 |
| Root 读 | 加密后 prefs 为乱码 |
⚠️ 常见坑:只加密 value,key 仍暴露业务含义 — 攻击者知道搜哪个 key。
💡 关键直觉:密钥不进 APK、不进代码 — 由 Keystore/Keychain 生成与持有。
allowBackup=false 与 Keychain Accessible 级别是基线下一节:通信 TLS、Pinning 与认证授权服务端化。
「存了加密」不等于「安全」,关键是三件事:算法对不对、密钥在哪、生命周期怎么管。算法上,对称加密用 AES-GCM(自带认证,防篡改),绝不用 ECB 或 DES;哈希口令用 bcrypt 或 argon2,绝不裸 MD5/SHA1。密钥必须由系统安全容器生成与持有:Android Keystore、iOS Keychain,私钥不可导出。自己硬编码一个 key 再混淆,等于把钥匙藏在代码里。
// 正确:密钥由 Keystore 生成,不可导出 val masterKey = MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() // 错误:硬编码密钥(混淆也救不了) // val key = "MySecretKey123456"
生命周期管理包含:会话结束后清除 Token、注销时删除所有密钥与缓存、卸载时确认无外部存储残留、App 更新时密钥迁移策略。密钥的「不可导出」属性是安全容器存在的意义——攻击者即使拿到设备,也拿不到 key material,只能逐次调用加密接口。
| 环节 | 要求 | 常见错误 |
|---|---|---|
| 算法 | AES-GCM | ECB/DES |
| 密钥 | Keystore/Keychain | 硬编码 |
| 传输 | 密钥不出设备 | 密钥发到服务端 |
| 生命周期 | 注销即删 | 残留旧版本数据 |
数据分级是存储安全的前提,落地分四步。第一步盘点:列出 App 所有写入磁盘的数据项;第二步分类:按敏感度分公开、半敏感、高敏感、极高四档;第三步定策:每档确定存储方式、加密要求、生命周期;第四步验证:用扫描与取证手段确认实际落盘与策略一致。实际项目中,Token、支付信息、生物特征这类高敏感数据最容易出问题——既要有加密存储,也要在注销与过期时彻底清除。
# 分级落地示例 数据: 用户昵称 -> 半敏感 -> 可明文但禁日志 数据: 会话 Token -> 高敏感 -> Keystore/Keychain,注销即删 数据: 银行卡 PAN -> 极高 -> 不落客户端,仅内存短用
验证是容易被跳过的最后一步。很多团队策略写得很全,实际代码还是把 Token 写进了普通 SharedPreferences。用 MobSF 或手工 grep 抽查发布包,策略与实现一致才算落地。
| 步骤 | 动作 | 验收 |
|---|---|---|
| 盘点 | 列出所有落盘数据 | 清单完整 |
| 分类 | 四档分级 | 无遗漏项 |
| 定策 | 存储+生命周期 | 策略明确 |
| 验证 | 抽查发布包 | 实现一致 |