Android-功能实现-011-Android获加密Room数据库

151 阅读2分钟

引自:如何在 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 保护。
  • 解密后的密钥,用毕会立刻从内存清除。

只要写几个类,小心处理加密密钥,就能大幅提升应用本地存储的安全性。这个方案是生产级别的,利用了成熟的加密原语,能帮用户数据抵御逆向工程和本地篡改。当然最安全的方法是如无必要,不要在设备上存极敏感数据!!