Implementing Local Data Encryption in a Mobile Application
We have been integrating local data encryption on iOS and Android for over 5 years. Clients often come with a problem: after a device is compromised, user data ends up in plain sight. The reason is simple — encryption is either not implemented or implemented with errors. An unencrypted SQLite database on Android is just a file: adb pull /data/data/com.yourapp/databases/app.db on a rooted device — and all data is readable with any SQLite browser. On iOS, the situation is slightly better due to the Data Protection API, but only if the developer hasn't forgotten to set the correct NSFileProtectionKey — and this is often overlooked. We guarantee that after our work, your data remains protected even with physical access to the device.
What we encrypt and how
The task breaks down into three independent layers: database, files, secrets.
Database. The standard is SQLCipher. A fork of SQLite with transparent AES-256 encryption at the page level. On Android it is integrated via net.zetetic:android-database-sqlcipher, on iOS via SQLCipher.xcframework. Room on Android works with SQLCipher through SupportOpenHelperFactory — switching involves simply replacing the factory in Room.databaseBuilder() and adding the key. The key is generated once, stored in Keystore/Keychain, never stored in SharedPreferences or UserDefaults in plaintext.
On first launch on Android:
val key = generateAes256Key() // via KeyGenerator with KeyStore provider val encryptedKey = encryptWithKeystore(key) // RSA/AES through AndroidKeyStore prefs.putString("db_key_enc", Base64.encode(encryptedKey)) Then every time the database is opened, we decrypt the key and pass it to SQLCipher. Without PRAGMA key, the database simply won't open.
Files. For images, PDFs, cache — AES-256-GCM via javax.crypto.Cipher on Android or CryptoKit.AES.GCM on iOS (Swift 5.5+). GCM is important: it provides both confidentiality and integrity authentication. CBC without a MAC is a poor choice, vulnerable to padding oracle attacks.
On Flutter, the flutter_secure_storage package is convenient for secrets and encrypt for files, but under the hood both use the same native APIs — wrappers, not replacements.
Secrets (API keys, tokens). Only Keychain (iOS) and Android Keystore. Not UserDefaults, not SharedPreferences, not AsyncStorage in React Native. Keychain on iOS is encrypted with keys bound to the Secure Enclave; Keystore on Android with API 23+ binds keys to TEE or SE — they cannot be exported even with root.
Why encrypting the database specifically is important
The database is the most vulnerable point. It typically stores user profiles, shopping lists, operation history. If an attacker gains physical access to the device (loss, theft) and bypasses the lock screen, an unencrypted database means full data access. SQLCipher makes the database unreadable without the key. Even when analyzing a RAM dump, the key does not surface because it is stored in hardware-backed Keystore.
Typical mistakes that break the entire scheme
First — incorrect protection class on iOS. FileProtectionType.complete means the file is inaccessible while the device is locked. But if the app receives a push notification in the background and tries to read the database — crash. Developers panic and switch to completeUnlessOpen or remove protection altogether. The correct solution is to separate data: critical under .complete, background operations under .completeUnlessOpen.
Second — storing the key alongside the data. We encountered a case where the database encryption key was in the same directory as the encrypted database, simply in a file key.bin. That's not encryption, that's renaming.
Third — using the user's password directly as the key. AES requires 128 or 256 bits. A password like "qwerty123" is not a key. A KDF is needed: PBKDF2 with a minimum of 100,000 iterations or Argon2id. On iOS — CommonCrypto.CCKeyDerivationPBKDF, on Android — SecretKeyFactory with PBKDF2WithHmacSHA256.
| Layer | iOS | Android |
|---|---|---|
| Database | SQLCipher + CoreData | SQLCipher + Room |
| Files | CryptoKit AES-GCM | javax.crypto.Cipher AES-GCM |
| Secrets | Keychain (Secure Enclave) | Android Keystore (TEE/SE) |
Integration with biometrics
An advanced option — the database encryption key is protected by biometrics via Keystore/Keychain. On Android: KeyGenParameterSpec.Builder with .setUserAuthenticationRequired(true) and .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG). The key is created once on first biometric login; then each time the app opens, the user authenticates, the key is unlocked from Keystore, and the database opens.
On iOS, similarly via kSecAttrAccessControl with SecAccessControlCreateWithFlags and the .biometryAny or .biometryCurrentSet flag. Difference: .biometryCurrentSet invalidates the key when new fingerprints are added — this is important for banking apps (according to official Apple documentation).
What is included in the work
Full-cycle encryption implementation turnkey:
- Inventory of stored data and classification by criticality.
- Key scheme selection and integration with Keystore/Keychain.
- Implementation of encryption layer for database, files, and secrets.
- Migration of existing data (if any).
- Testing all scenarios: app update, restore from backup, biometric changes.
- Documentation and training for your team.
Process
We start with an inventory: what is stored where, whether encryption already exists, which data falls under requirements (PCI DSS, GDPR, local regulations). Then — key scheme selection, encryption layer implementation, integration with the existing storage. Separately — testing scenarios: app update, restore from backup, user biometric changes.
The timeline depends on data volume and the existence of a current storage scheme. If a database already exists and needs migration to SQLCipher — 3–5 days including testing. File cache encryption and Keychain integration — an additional 1–2 days. A full scheme from scratch for a new project is faster.
We will assess your project for free. Contact us — we will analyze your current storage scheme and offer the optimal solution with a security guarantee.







