almasumdev/awesome-mobile-security-agent-skills

encrypted-databases

Encrypting local databases on mobile with SQLCipher, Room + EncryptedFile, or Realm encryption.

查看源码
仓库原始内容

按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。

Encrypted Databases on Mobile

Instructions

Plain SQLite and plain Realm files are readable by anyone with filesystem access — including forensic tools and, on rooted/jailbroken devices, other apps. Encrypt at rest for anything sensitive.

1. When to Encrypt

Encrypt when the DB contains:

  • Session tokens, refresh tokens, or API credentials.
  • PII (email, phone, address, national IDs).
  • Financial / health records.
  • User-generated content marked private.

Do not encrypt purely public / cached content; it wastes CPU and complicates debugging.

2. Key Management (The Actual Hard Part)

  • Generate a random 256-bit key once per install.
  • Wrap it with a keystore-backed key (Android Keystore / iOS Keychain).
  • Store the wrapped blob in EncryptedSharedPreferences / Keychain.
  • Never derive the DB key from a constant string or from android_id.

3. Android: Room + SQLCipher

kotlin
// build.gradle.kts
// implementation("net.zetetic:sqlcipher-android:4.6.1")
// implementation("androidx.sqlite:sqlite:2.4.0")

val passphrase: ByteArray = KeyStoreHelper.loadOrCreateDbKey() // 32 bytes
val factory = SupportOpenHelperFactory(passphrase, null, false)

val db = Room.databaseBuilder(ctx, AppDatabase::class.java, "app.db")
    .openHelperFactory(factory)
    .build()

// Zero the passphrase as soon as Room has it.
passphrase.fill(0)

4. Android: Room + EncryptedFile (Alternative)

For small stores where full-text queries are not required, use Jetpack EncryptedFile:

kotlin
val file = EncryptedFile.Builder(
    ctx, File(ctx.filesDir, "notes.bin"), masterKey,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB,
).build()

file.openFileOutput().use { it.write(payload) }

This is simpler than SQLCipher but loses SQL query capabilities.

5. iOS: SQLCipher via GRDB

swift
var config = Configuration()
config.prepareDatabase { db in
    let key = try KeychainHelper.loadOrCreateDbKey() // 32-byte Data
    try db.usePassphrase(key)
}
let dbQueue = try DatabaseQueue(path: path, configuration: config)

For Core Data, wrap the store with NSPersistentStoreFileProtectionKey: FileProtectionType.complete — NOT equivalent to SQLCipher, but acceptable when the device is locked.

6. Realm

kotlin
val config = RealmConfiguration.Builder(schema = setOf(User::class))
    .name("user.realm")
    .encryptionKey(KeyStoreHelper.loadOrCreateRealmKey()) // 64 bytes
    .build()
val realm = Realm.open(config)

Realm requires a 64-byte key. Losing it means the DB is unrecoverable — plan for re-sync from server on key loss.

7. Migration From Plaintext

If you inherit a plaintext DB:

  1. Open it plaintext.
  2. Attach a new encrypted DB with the new key.
  3. INSERT INTO encrypted.table SELECT * FROM plain.table; for each table.
  4. Close, delete the plaintext file, rename encrypted → canonical path.
  5. Run on a background thread with progress UI; this can take minutes on large DBs.

8. Debug vs Release

  • In debug builds you may want to disable encryption to allow inspection with DB Browser / Stetho. Gate this on BuildConfig.DEBUG / #if DEBUG so it cannot ship.
  • Never commit a debug key that also unlocks production dumps.

Checklist

  • [ ] The DB key is random (not derived from a constant or device ID).
  • [ ] The DB key is wrapped by Keystore / Keychain and never stored plaintext on disk.
  • [ ] SQLCipher / Realm encryption is enabled on any DB containing PII or credentials.
  • [ ] A documented migration exists for moving users from plaintext to encrypted DBs.
  • [ ] Key-loss UX is handled (re-sync or re-login) rather than silent data corruption.
  • [ ] Debug-only relaxations cannot ship to production.
来自同一仓库

更多 Skills

全部 Skills