数据存储安全:SharedPreferences加密、Room数据库加密、KeyStore使用、文件加密存储
数据存储这块,说实话是很多安全漏洞的重灾区。我见过太多应用直接把用户密码、Token明文扔进SharedPreferences,简直就像把家门钥匙挂在门外。今天咱们就聊聊怎么把这些存储手段武装起来。
SharedPreferences加密:别让明文裸奔
SharedPreferences用起来方便,但默认是明文存储的。你想想看,只要手机被root,或者有人能读到你的应用数据目录,所有信息一览无余。
我个人习惯用 EncryptedSharedPreferences,这是Android官方提供的方案。它背后用的是AES-256加密,密钥由Android Keystore管理。
// 初始化加密的SharedPreferences
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", "your_token").apply()
val token = sharedPreferences.getString("token", null)
这里有个坑——PrefKeyEncryptionScheme和PrefValueEncryptionScheme要选对。我刚开始用的时候选错了,结果key和value都用了同样的加密模式,后来发现key的加密应该用AES256_SIV,value用AES256_GCM。为什么?因为key需要确定性加密,方便查找;value需要随机化加密,更安全。
Room数据库加密:SQLCipher来帮忙
Room本身不提供加密功能,但我们可以用SQLCipher。说白了,SQLCipher就是给SQLite加了一层透明的加密解密层。
我在项目中遇到过一个问题:用户数据量很大,加密后性能下降明显。后来发现是没配置好加密参数。
// 在Room中集成SQLCipher
val passphrase = "your_secure_passphrase" // 建议从KeyStore获取
val database = Room.databaseBuilder(
context,
AppDatabase::class.java,
"encrypted_db"
)
.openHelperFactory(SupportFactory(passphrase))
.build()
性能优化方面,我建议:
- 设置合适的页面大小(默认4096字节,可以试试1024)
- 使用WAL模式提升并发性能
- 定期执行PRAGMA optimize
KeyStore使用:密钥的保险箱
Android KeyStore是什么?它是系统级的硬件安全模块。密钥一旦存入,就永远无法以明文形式导出。嗯,这里要注意:KeyStore只负责存密钥,不负责加密数据。
我曾经犯过一个错误:把密钥直接存在SharedPreferences里,结果被逆向工具轻松提取。后来改用KeyStore,才真正解决了密钥泄露问题。
// 生成并存储密钥到KeyStore
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
val keyGenSpec = KeyGenParameterSpec.Builder(
"my_key_alias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
keyGenerator.init(keyGenSpec)
keyGenerator.generateKey()
// 使用密钥进行加密
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val secretKey = keyStore.getKey("my_key_alias", null) as SecretKey
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val iv = cipher.iv // 保存IV,解密时需要
val encryptedData = cipher.doFinal(plaintext)
文件加密存储:大文件的保护方案
对于图片、视频这类大文件,SharedPreferences和Room都不合适。我们需要直接对文件进行加密。
我推荐使用 Android File Encryption 或者自己封装AES-GCM。为什么选GCM?因为它同时提供加密和完整性校验,能防止数据被篡改。
// 文件加密示例(使用AES-GCM)
fun encryptFile(inputFile: File, outputFile: File, secretKey: SecretKey) {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
// 将IV写入文件头部
val iv = cipher.iv
outputFile.outputStream().use { output ->
output.write(iv)
inputFile.inputStream().use { input ->
val cipherOutputStream = CipherOutputStream(output, cipher)
input.copyTo(cipherOutputStream)
cipherOutputStream.close()
}
}
}
fun decryptFile(inputFile: File, outputFile: File, secretKey: SecretKey) {
inputFile.inputStream().use { input ->
// 先读取IV
val iv = ByteArray(12) // GCM标准IV长度
input.read(iv)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
outputFile.outputStream().use { output ->
val cipherInputStream = CipherInputStream(input, cipher)
cipherInputStream.copyTo(output)
cipherInputStream.close()
}
}
}
File.delete()还不够,建议用FileOutputStream覆写随机数据后再删除,防止被恢复。
实际项目中,我通常这样组合使用:
- 小数据(配置、Token) → EncryptedSharedPreferences
- 结构化数据(用户信息、聊天记录) → 加密Room数据库
- 密钥管理 → Android KeyStore
- 大文件(图片、视频) → AES-GCM文件加密
最后说一句:加密不是万能的,但不加密是万万不能的。你想想看,用户把数据托付给你,你至少得帮人家锁好门吧?