19、Security-Crypto加密库:文件级加密与密钥管理

说到Android安全,很多人第一反应就是网络传输要加密。但说实话,本地存储的安全往往更容易被忽视。我见过不少项目,数据库里存着用户隐私,SharedPreferences里躺着Token,结果反编译APK就能直接读出来——这太危险了。

Android官方其实早就给了我们一套完整的本地加密方案,就是Security-Crypto库。它封装了文件级加密、密钥管理,还集成了Android Keystore系统。今天我就带你彻底搞懂它。

核心要点:Security-Crypto库不是让你自己写加密算法,而是提供了一套开箱即用的安全存储方案。你只需要关心业务逻辑,加密细节交给它。

19.1 为什么需要文件级加密?

先问个问题:你的App里哪些数据需要保护?

  • 用户登录Token、Session ID
  • 支付信息、银行卡号
  • 聊天记录、私密文件
  • App配置、业务敏感数据

这些数据如果明文存储,一旦手机被root或者被恶意应用读取,后果很严重。我曾在一次安全审计中发现,某个知名App把用户身份证号直接写在SharedPreferences里——嗯,那个版本很快就被紧急下架了。

文件级加密的好处在于:即使攻击者拿到了文件,没有密钥也读不出内容。而且加密和解密对开发者几乎是透明的,你只需要在读写时调用对应的API。

19.2 EncryptedSharedPreferences:安全的首选

SharedPreferences是我们最常用的轻量级存储方案。但默认情况下,它存的是明文。如果你在里头存了敏感信息,那基本等于把钥匙挂在门上。

EncryptedSharedPreferences就是它的安全升级版。它使用AES-256加密数据,密钥由Android Keystore保护。

19.2.1 基本使用

先加依赖:

implementation "androidx.security:security-crypto:1.1.0-alpha06"

然后创建实例:

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.edit().putString("token", "your_token_here").apply()

// 读取数据
val token = sharedPreferences.getString("token", null)

你看,使用方式和普通SharedPreferences几乎一样。唯一的区别是多了个MasterKey和加密方案的选择。

我的建议:项目中所有涉及用户身份、支付、隐私的SharedPreferences,都应该换成EncryptedSharedPreferences。性能开销很小,安全收益却很大。

19.2.2 密钥方案选择

方案 说明 适用场景
AES256_SIV 密钥加密,确定性加密 适合Key的加密,因为Key需要可查询
AES256_GCM 值加密,带认证标签 适合Value的加密,提供完整性校验

这里有个细节:Key的加密用了SIV模式,因为它是确定性的——同样的Key加密结果相同,这样你才能通过Key查询到对应的值。而Value用GCM模式,每次加密结果不同,安全性更高。

19.3 EncryptedFile:大文件的安全读写

SharedPreferences适合小数据,但如果你要存图片、视频、数据库文件,就得用EncryptedFile了。

它同样基于AES-256加密,支持流式读写。这意味着即使文件很大,也不会一次性加载到内存。

19.3.1 写入加密文件

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

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

encryptedFile.openFileOutput().use { outputStream ->
    outputStream.write("这是加密内容".toByteArray())
}

19.3.2 读取加密文件

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

encryptedFile.openFileInput().use { inputStream ->
    val content = inputStream.readBytes().toString(Charsets.UTF_8)
    Log.d("EncryptedFile", "解密内容: $content")
}

注意:EncryptedFile的读写都是流式的,所以不要忘了用use块自动关闭流。我曾经在早期版本中忘记关闭,导致文件锁未释放,后续读写全部失败——排查了半天才发现是流没关。

19.4 Android Keystore系统集成

说了这么多,你可能好奇:密钥存在哪里?

答案是Android Keystore。这是一个硬件隔离的安全区域,密钥一旦生成,就无法从App进程中导出。即使手机被root,攻击者也拿不到密钥本身。

Security-Crypto库的MasterKey默认就是存储在Keystore中的。你不需要手动管理密钥生命周期,库帮你搞定了。

19.4.1 MasterKey的构建细节

val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .setRequestStrongBoxBacked(true)  // 要求使用硬件安全模块
    .setUserAuthenticationRequired(true)  // 要求用户生物识别验证
    .setUserAuthenticationValidityDurationSeconds(300)  // 验证有效时长
    .build()

这里有几个选项值得注意:

  • StrongBox:如果设备支持,密钥会存储在独立的硬件安全芯片中,安全性最高
  • 用户认证:可以要求每次使用密钥前进行指纹或人脸验证,适合支付场景
  • 有效期:用户认证后一段时间内可重复使用,避免频繁弹窗

避坑指南:我曾经在项目中开启了setUserAuthenticationRequired(true),但没设置有效期。结果每次读取SharedPreferences都要弹指纹——用户体验极差。后来改成300秒有效期,既安全又流畅。

19.5 知识体系总览

下面这张图帮你理清Security-Crypto库的整体架构:

Security-Crypto 加密库架构 应用层 EncryptedSharedPreferences · EncryptedFile 加密引擎层 AES-256 GCM/SIV · 流式加密 · 认证加密 密钥管理层 MasterKey · 密钥派生 · 密钥轮换 Android Keystore · StrongBox

从这张图可以清楚看到:应用层只管调用API,加密引擎处理算法细节,密钥管理层保证密钥安全,最底层的Keystore提供硬件级保护。每一层各司其职,你不需要关心底层实现。

19.6 最佳实践总结

最后,分享几条我在实战中总结的经验:

  1. 小数据用EncryptedSharedPreferences,大数据用EncryptedFile——别混用,也别滥用
  2. MasterKey尽量用默认配置,除非你有特殊的安全需求(比如支付场景需要用户认证)
  3. 不要自己存密钥——永远依赖Keystore,别把密钥写在代码里或Asset中
  4. 注意Android版本兼容:Security-Crypto库最低支持API 23(Android 6.0),如果你的App还要支持更低版本,需要额外处理
  5. 测试时记得处理异常:加密操作可能抛出GeneralSecurityException,别让App直接崩溃

一个小技巧:如果你在调试阶段想查看加密文件的内容,可以用adb shell进到data目录,但你会发现文件内容全是乱码——这就是加密的效果。别慌,不是文件坏了。

好了,关于Security-Crypto加密库的核心内容就这些。说白了,它就是Android官方给我们的一把安全锁,用好了能挡住绝大多数本地数据泄露的风险。下次写代码时,记得问自己一句:这个数据需要加密吗?


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