DEV Community

YADNYESH RANA
YADNYESH RANA

Posted on

Encrypting Room SQLite Databases with SQLCipher and Biometric Keys

Storing raw user credentials, OAuth tokens, or sensitive transactions in a standard local SQLite database is a major security vulnerability. Anyone with a rooted device or access to a backup can extract the database file and read the tables in plain text.

To secure your local storage, you need to encrypt the database using SQLCipher and generate the encryption keys inside the device's hardware-backed Keystore.

Here is the exact implementation details.


1. Setup SQLCipher Dependency
First, add the SQLCipher library to your module's dependency configuration:

// build.gradle.kts
dependencies {
    implementation("net.zetetic:android-database-sqlcipher:4.5.4")
    implementation("androidx.sqlite:sqlite-ktx:2.4.0")
}
Enter fullscreen mode Exit fullscreen mode

2. Generating the Encryption Key in the TEE (Keystore)
Never hardcode your database passphrase in the Kotlin source. Instead, generate an AES key inside the device's secure hardware enclave—the Trusted Execution Environment (TEE)—and require biometric verification before the key can be accessed.

fun generateSecretKey() {
    val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }

    if (!keyStore.containsAlias("DatabaseKeyAlias")) {
        val keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES, 
            "AndroidKeyStore"
        )

        val spec = KeyGenParameterSpec.Builder(
            "DatabaseKeyAlias",
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setUserAuthenticationRequired(true) // Requires biometrics (fingerprint/face)
            .setUserAuthenticationValidityDurationSeconds(-1) // Authenticate per session/call
            .setInvalidatedByBiometricEnrollment(true) // Key invalidates if new fingers enrolled
            .build()

        keyGenerator.init(spec)
        keyGenerator.generateKey()
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Initializing Room with the SQLCipher Open Helper
Once the key is generated in the hardware, construct a helper factory that feeds the passphrase to SQLCipher's open helper during database initialization:

fun getEncryptedDatabase(context: Context, passphraseString: String): AppDatabase {
    // Convert passphrase string to byte array
    val passphrase = SQLiteDatabase.getBytes(passphraseString.toCharArray())

    // Create the SQLCipher OpenHelper Factory
    val factory = SupportOpenHelperFactory(passphrase)

    return Room.databaseBuilder(
        context,
        AppDatabase::class.java,
        "secure_app.db"
    )
        .openHelperFactory(factory) // Wraps standard Room with SQLCipher
        .build()
}
Enter fullscreen mode Exit fullscreen mode

4. Handling Biometric Key Access
To open the database, trigger the system biometric prompt and pass the resulting cipher object to decrypt your passphrase:

fun decryptPassphrase(cipher: Cipher, encryptedData: ByteArray): String {
    val decryptedBytes = cipher.doFinal(encryptedData)
    return String(decryptedBytes, Charsets.UTF_8)
}
Enter fullscreen mode Exit fullscreen mode

If the device is rooted or someone tries to clone the app, the SQLite database file remains completely unreadable (displays as corrupted binary noise).


Open-Source Reference
This security implementation is part of the open-source Android System Design & Architecture Checklist. You can clone the full repository of cryptographic configurations, SSL pinning helpers, and offline sync transaction queues here:

👉 GitHub: Android System Design & Architecture Checklist (A print-ready, A4 PDF version is also pinned in the repository description).

Top comments (0)