引自:如何在 Android 中使用 SQLCipher 加密 Room 数据库
在开发 Android 应用时,我们常会用 Room 做本地数据存储。一旦应用被逆向攻破,提取数据会如同探囊取物。加密 Room 数据库是守护用户数据的关键一步。本文会带你了解用 SQLCipher、Android Keystore 给 Room 数据库加上密。
为啥要加密 Room 数据库?
Room 本身不自带加密功能。不过咱们能用 SQLCipher for Android ,它是 SQLite 的替代方案,内置加密能力,可靠、有人维护、广受信任。
步骤 1:添加依赖
先在 build.gradle 文件里加必要依赖:
// SQLCipher
implementation "net.zetetic:sqlcipher-android:4.9.0" // 或最新版本
// Room
implementation("androidx.room:room-runtime:$room_version")
ksp("androidx.room:room-compiler:$room_version")
步骤 2:安全生成加密密钥
不要把密码硬编码!应该运行时生成随机的 256 位密钥,并用 Android Keystore 保护它。
错误示范:
val key = SQLiteDatabase.getBytes("your-password".toCharArray())
这么做不安全,密码能从 APK 里被提取出来。
正确做法:
val sqlCipherKey = ByteArray(32)
SecureRandom().nextBytes(sqlCipherKey)
但密钥还得安全存储,光用 SharedPreferences 不够。
步骤 3:用 Android Keystore 保护密钥
存储前,先用 Android Keystore 加密生成的密钥。 创建 SqlCipherKeyManager:
class SqlCipherKeyManager(
privateval sharedPreferences: SharedPreferences
) {
privateval keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
init {
initialize()
}
privatefun initialize() {
generateKeyStoreKeyIfNeeded()
if (!sharedPreferences.contains("encrypted_key")) {
generateAndEncryptSqlCipherKey()
}
}
privatefun generateKeyStoreKeyIfNeeded() {
if (!keyStore.containsAlias("sqlcipher_keystore_key")) {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES)
val keySpec = KeyGenParameterSpec.Builder(
"sqlcipher_keystore_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
keyGenerator.init(keySpec)
keyGenerator.generateKey()
}
}
privatefun generateAndEncryptSqlCipherKey() {
val secretKey = getSecretKey("sqlcipher_keystore_key")
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val sqlCipherKey = ByteArray(32)
SecureRandom().nextBytes(sqlCipherKey)
val encryptedKey = cipher.doFinal(sqlCipherKey)
val iv = cipher.iv
sharedPreferences.edit {
putString("encrypted_key", Base64.encodeToString(encryptedKey, Base64.NO_WRAP))
putString("encryption_iv", Base64.encodeToString(iv, Base64.NO_WRAP))
}
// 清空内存里的密钥
sqlCipherKey.fill(0)
}
privatefun getDecryptedSqlCipherKey(): ByteArray {
val encryptedKey = Base64.decode(
sharedPreferences.getString("encrypted_key", null)!!,
Base64.NO_WRAP
)
val iv = Base64.decode(
sharedPreferences.getString("encryption_iv", null)!!,
Base64.NO_WRAP
)
val secretKey = getSecretKey("sqlcipher_keystore_key")
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
val decryptedKey = cipher.doFinal(encryptedKey)
return decryptedKey
}
privatefun getSecretKey(keyAlias: String): SecretKey {
return keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry
}.secretKey
fun getSupportFactory(): SupportOpenHelperFactory {
val decryptedKey = getDecryptedSqlCipherKey()
return WipeAfterUseSupportFactory(decryptedKey)
}
privateclass WipeAfterUseSupportFactory(
privateval decryptedKey: ByteArray
) : SupportOpenHelperFactory(decryptedKey) {
overridefun create(configuration: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper {
val helper = super.create(configuration)
decryptedKey.fill(0)
return helper
}
}
}
步骤 4:从内存清除敏感数据
为避免密钥在内存停留过久,自定义 SupportOpenHelperFactory:
class DisposableKeySupportFactory(private val decryptedKey: ByteArray) :
SupportOpenHelperFactory(decryptedKey) {
override fun create(configuration: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper {
val helper = super.create(configuration)
decryptedKey.fill(0)
return helper
}
}
Room 一用完,密钥就会立刻从内存清除。
步骤 5:和 Room 整合起来
用加密后的 SupportFactory 初始化 Room:
// 加载 SQLCipher 库(重要!)
System.loadLibrary("sqlcipher")
val sqlCipherKeyManager = SqlCipherKeyManager(sharedPreferences)
val db = Room.databaseBuilder(context, YourDatabase::class.java, "your-db-name")
.openHelperFactory(sqlCipherKeyManager.getSupportFactory())
.build()
总结
- 这套方法用 SQLCipher 给整个 Room 数据库做了 AES-256 加密。
- 加密密钥随机生成,并用 Android Keystore 保护。
- 解密后的密钥,用毕会立刻从内存清除。
只要写几个类,小心处理加密密钥,就能大幅提升应用本地存储的安全性。这个方案是生产级别的,利用了成熟的加密原语,能帮用户数据抵御逆向工程和本地篡改。当然最安全的方法是如无必要,不要在设备上存极敏感数据!!