ADR-0010: SQLCipher + Android Keystore encryption
- Status: Accepted
- Date: 2026-08-11
- Deciders: maintainers
- Related: data-layer.md, security.md
Context
The app stores sensitive notification text (amounts, merchants, account references). The data must be encrypted at rest on-device, with the key unavailable to other apps and unrecoverable after app-data wipe.
Decision
- Room runs on SQLCipher (
net.zetetic:sqlcipher-android) viaSupportOpenHelperFactory(the app callsSystem.loadLibrary("sqlcipher")before opening). - The SQLCipher passphrase is a random 32-byte value generated on first
launch, encrypted with an Android Keystore AES-GCM key
(
autobudget_sqlcipher_key, 256-bit,AES/GCM/NoPadding), stored base64 inautobudget_cryptoSharedPreferences. - Backups are disabled:
android:allowBackup="false"plusdata_extraction_rules.xml/backup_rules.xml.
Consequences
- The key material never leaves the device; the ciphertext and the key are destroyed together on app-data wipe or uninstall — history is permanently unrecoverable.
- The DB cannot be read by other apps, and cannot be restored onto another device via backup.
sqlcipher-androidships 16 KB-aligned native libraries, so no build-time ELF patching is needed (see ADR-0011).
Alternatives considered
- Plain Room — rejected: notification text at rest unencrypted.
- Keystore-bound symmetric key as the passphrase directly — rejected: an attacker with the ciphertext + a way to invoke the Keystore could decrypt; wrapping a random passphrase adds a layer and survives key rotation.