16、Security-Crypto:EncryptedSharedPreferences、文件加密、密钥管理

说到 Android 上的数据安全,很多人第一反应就是「我用数据库存,别人总看不到吧?」。嗯,太天真了。我见过太多项目,SharedPreferences 里直接存明文 token,数据库文件被 root 过的手机一读就全暴露了。今天我们来聊聊 Jetpack 里的 Security-Crypto 库,说白了就是帮你把数据「锁起来」——而且是官方帮你锁。

16.1 为什么需要 Security-Crypto?

你想想看,Android 设备上存储的数据,尤其是用户隐私信息,比如登录 token、支付密码、身份证号,一旦被恶意应用或者通过 adb backup 拿到,后果很严重。SharedPreferences 默认是明文存储的,文件路径在 /data/data/包名/shared_prefs/ 下,root 过的手机直接 cat 就能看到。

Security-Crypto 库提供了两样核心武器:

  • EncryptedSharedPreferences:自动加密的键值对存储
  • 文件加密:对任意文件进行加解密操作

背后依赖的是 Android Keystore 系统,密钥由硬件安全模块(TEE)保管,应用层拿不到原始密钥材料。这才是真正的「锁在保险柜里」。

核心原则:密钥永远不要硬编码在代码里,也不要存在 SharedPreferences 里。Keystore 是唯一正确的选择。

16.2 EncryptedSharedPreferences 实战

先加依赖。在 build.gradle 里引入:

dependencies {
    implementation "androidx.security:security-crypto:1.1.0-alpha06"
}

然后创建实例。我个人习惯把初始化封装成一个单例,避免到处传 Context:

val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val sharedPreferences = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

// 使用方式跟普通 SharedPreferences 一模一样
sharedPreferences.edit().putString("token", "eyJhbGciOiJIUzI1NiIs...").apply()
val token = sharedPreferences.getString("token", null)

注意看,MasterKey 的构建用了 AES256_GCM 方案,这是 Android 推荐的对称加密算法。密钥会自动生成并存入 Keystore,你不需要关心它长什么样。

小提示:EncryptedSharedPreferences 的 key 和 value 是分开加密的。key 使用 AES256_SIV 算法(保证确定性加密,方便查找),value 使用 AES256_GCM(提供认证加密,防篡改)。这个设计很巧妙,既保证了查询效率,又保证了数据完整性。

16.3 文件加密:不只是存个字符串

有时候我们需要加密的不只是键值对,而是一整个文件——比如用户下载的 PDF 合同、拍照的身份证图片。Security-Crypto 提供了 EncryptedFile 类。

我在项目中遇到过这样一个场景:用户上传身份证照片前,需要先加密存储在本地,等网络恢复后再上传。直接用 EncryptedFile 搞定:

val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val encryptedFile = EncryptedFile.Builder(
    context,
    File(context.filesDir, "id_card.jpg"),
    masterKey,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

// 写入加密数据
encryptedFile.openFileOutput().use { outputStream ->
    // 假设 bitmap 是用户拍摄的身份证图片
    bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
}

// 读取解密数据
encryptedFile.openFileInput().use { inputStream ->
    val decryptedBitmap = BitmapFactory.decodeStream(inputStream)
}

这里有个细节:AES256_GCM_HKDF_4KB 中的 4KB 指的是文件分块大小。加密时文件会被切成 4KB 的块,每块独立加密。这样做的好处是——你不需要一次性把整个文件加载到内存里,对大文件非常友好。

曾经踩过的坑:EncryptedFile 的读写流必须用 use 块确保关闭。我早期有个版本忘了关流,导致文件句柄泄漏,应用运行几天后突然无法写入新文件。排查了半天才发现是流没关。血的教训。

16.4 密钥管理:最容易被忽视的环节

加密算法再强,密钥泄露就全完了。Android Keystore 系统提供了硬件级保护,但开发者还是有一些事情要注意。

我总结了几条关键原则:

原则 说明 我的建议
不要硬编码密钥 密钥字符串写在代码里,反编译就暴露 用 MasterKey.Builder 自动生成
区分设备绑定 密钥是否绑定到设备?绑定后无法迁移 敏感数据用绑定密钥,非敏感用可迁移
密钥轮换 定期更换密钥,降低泄露风险 每次应用更新时重新生成 MasterKey
生物识别保护 密钥使用前需要指纹/人脸验证 支付类场景强烈建议开启

如果你需要密钥绑定到生物识别,可以这样配置:

val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .setUserAuthenticationRequired(true) // 需要用户验证
    .setUserAuthenticationValidityDurationSeconds(30) // 30秒内有效
    .build()

这样每次读取加密数据时,系统会弹出指纹/人脸验证对话框。嗯,用户体验上会多一步操作,但安全性提升了一个量级。

16.5 知识体系总览

下面这张图把 Security-Crypto 的核心脉络梳理清楚了。你可以看到,所有加密操作都依赖 Keystore 生成的 MasterKey,然后通过 EncryptedSharedPreferences 或 EncryptedFile 对外提供服务。

Security-Crypto 核心架构 应用层(你的代码) 调用 EncryptedSharedPreferences / EncryptedFile 读写数据 Security-Crypto 加密层 AES256_GCM / AES256_SIV / HKDF 分块加密 自动处理 IV、认证标签、分块读写 Android Keystore(硬件安全模块) MasterKey 生成与存储,TEE 保护,生物识别绑定 密钥永不离开 Keystore,应用层只拿到加密后的数据

16.6 避坑指南

最后分享几个我实际踩过的坑,希望能帮你省点时间:

  • 曾经遇到过:在 Android 6.0 以下设备上,Keystore 不支持 AES256_GCM,会直接崩溃。解决方案是运行时检测 SDK 版本,低版本回退到 AES128_GCM
  • 曾经遇到过:EncryptedSharedPreferences 的 apply() 是异步的,如果你紧接着杀掉进程,数据可能没写进去。重要数据建议用 commit() 确保同步写入。
  • 曾经遇到过:文件加密时,如果文件超过 2GB,EncryptedFile 的分块机制会导致性能急剧下降。大文件建议自己实现流式加密,或者用 NDK 层处理。
  • 曾经遇到过:用户清除应用数据后,Keystore 里的密钥也会被清除。如果你有云端同步需求,记得把密钥用用户密码派生后上传,不要直接存 Keystore 里的原始密钥。
我的习惯:每次发布新版本时,在 Application.onCreate 里检查 MasterKey 是否存在,如果不存在就重新生成。这样即使用户覆盖安装,密钥也能自动更新,旧数据用旧密钥解密后重新加密。

好了,Security-Crypto 的核心内容就这些。说白了就是三件事:用 EncryptedSharedPreferences 加密键值对、用 EncryptedFile 加密文件、用 Keystore 管好密钥。记住一点——不要自己写加密算法,用官方库永远是最安全的。

公众号:蓝海资料掘金营,微信 deep3321