Most mobile apps keep a local database: a SQLite file behind Room or Core Data, a Realm store, or a key-value cache that grew into one. It usually holds more than anyone planned, including messages, profile details, order history and cached API responses, and by default it sits on disk in a form that anyone with access to the file can read. Encrypting it is a real control, with real costs, and the useful questions are when it matters, which approach to use, and where the key lives.
Short answer
Both iOS and Android already encrypt the whole device storage when a passcode is set, so a local database is not readable from a powered-off, locked phone. What platform encryption does not cover is access while the device is in use: backups, a compromised or rooted device, debugging tools and forensic extraction of a phone in use. Database-level encryption, such as SQLCipher for SQLite, closes that gap for sensitive data, provided the key lives in the Keychain or Android Keystore rather than in the app. For low-sensitivity caches it is often not worth the cost; for health, financial, messaging or identity data it usually is.
What platform encryption already gives you
Start with what is free. On iOS, files are protected by Data Protection classes that tie each file's encryption to the device passcode. The default class for most app files makes them readable once the passcode has been entered after a reboot, which is a reasonable balance for most data and weaker than many teams assume. The stronger class makes files readable only while the screen lock is open. The trade-off is explained in the post on iOS Data Protection classes, and it is worth reading before adding anything heavier.
Android uses file-based encryption on modern devices, again tied to the user's lock screen credentials. Files in your app's private storage are encrypted at rest and decrypted for your app while the user is signed in to the device.
Together these mean a stolen, locked, powered-off phone is not a meaningful risk for most local databases. That removes the most common justification people give for database encryption and leaves the more specific ones.
Where the platform protection stops
Platform encryption protects data at rest against someone who does not have the device credentials. It does nothing once the user is signed in to the device and the operating system is decrypting files for legitimate use. The cases that matter in practice are these:
- A rooted or jailbroken device. Another process with root privileges can read your app's private files directly.
- Device backups. Depending on configuration, app data can end up in local computer backups or cloud backups, outside the device's protection. On Android, Auto Backup includes app files by default unless you exclude them.
- Forensic extraction. Tools used on a phone that is in use, or was in use shortly before, can copy app containers.
- Malware or debugging access. A debug build, or a device with developer access enabled, makes the container easy to inspect.
- Shared or handed-over devices. A tablet used by several staff members, or a phone passed to a family member, is in the hands of someone who is not the account holder.
The OWASP MASVS storage requirements treat sensitive data at rest as something the app itself must protect, beyond what the platform provides, which is the standard most security reviews and enterprise customers will measure against.
Deciding whether your database needs its own encryption
The decision is about the data, not the database. Three questions settle most cases.
What would a copy of the file reveal?
Open a copy of your database in any SQLite browser and read it as an outsider would. If it shows message bodies, health readings, account numbers, location history, identity documents or authentication material, it qualifies as sensitive. If it shows a product catalogue and some UI preferences, it does not.
Does the data need to be on the device at all?
The cheapest encryption is not storing the data. Many apps cache full API responses for convenience, including fields the screens never show. Trimming what is stored, and keeping truly sensitive values server-side with short-lived fetches, often removes the need for encryption entirely. The post on sensitive data in app caches and temp files covers the same principle for files outside the database.
Who will ask?
Enterprise customers, regulated sectors and security reviews increasingly ask directly whether locally stored data is encrypted beyond the platform default. If your buyers ask, a clear yes with a clear key management story is worth more than a debate about threat models.
Approaches compared
| Approach | What it encrypts | Key handling | Main cost | Fits |
|---|---|---|---|---|
| Platform encryption only | Whole file system at rest | Device credentials | None | Low-sensitivity caches |
| Stronger iOS protection class | Files while device is locked | Device credentials | Background access limits | Data not needed in background |
| SQLCipher or equivalent | Entire database file | App key in Keychain or Keystore | Size, speed, key management | Sensitive structured data |
| Field-level encryption | Selected columns or values | App key in Keychain or Keystore | Querying encrypted fields is hard | A few sensitive fields in a large database |
| No local storage | Nothing to encrypt | Server-side | Offline access is lost | Highly sensitive data |
SQLCipher is the most common choice for full database encryption on both platforms. It is a SQLite extension that encrypts every page of the database file transparently, so queries work as normal once the database is opened with the right key. Android apps using Room can plug it in through a support factory, and iOS apps can use it underneath SQLite wrappers. Core Data does not encrypt its store by itself, so teams that need it either rely on Data Protection classes or move the sensitive parts to an encrypted store.
Field-level encryption is the targeted option: encrypt only the sensitive columns with a key from the Keystore or Keychain, leave the rest readable. It keeps the database small and fast and makes searching or sorting by those fields impractical, which is often acceptable for things like notes or identity numbers.
Where the key lives decides everything
Encryption moves the problem from protecting the data to protecting the key. A database encrypted with a key that is hard-coded in the app, derived from the package name, or stored in plain preferences next to the database is not protected in any meaningful sense, because anyone who can read the database can read the key too.
The key belongs in the platform key store. On Android, the Android Keystore holds keys that cannot be extracted from the device, ideally backed by secure hardware; the usual pattern is to generate a random database key, encrypt it with a Keystore key, and store only the wrapped version. On iOS, the Keychain stores the random database key with an accessibility setting that matches when the app needs the database.
Two details trip teams up. First, the key must be random and generated on the device, not derived from anything predictable. Second, the Keychain item's accessibility must match the app's needs: a key only available while the device is in use breaks background sync, while a key available at all times weakens the protection. The OWASP MASVS cryptography requirements cover key generation and storage in detail.
Should the key depend on the user's PIN or biometrics?
Binding the key to user authentication gives the strongest protection, since the database cannot be opened without the user present. It also means no background work on that data, and it makes key loss real: if the binding is invalidated, for example when biometric enrolment changes, the database becomes unreadable and must be rebuilt from the server. That works well for data the server can restore and badly for offline-only data.
The costs teams underestimate
Encrypted databases are slower, though for typical mobile workloads the difference is modest once the database is open; the larger cost is usually the key derivation at open time, which should happen once per session rather than per query. App size grows by the size of the crypto library. Migration is the real work: an existing plain database has to be exported into an encrypted one on first launch of the new version, safely, with a fallback if the process is interrupted.
Debugging changes too. Developers can no longer open the production database file in a browser to inspect a bug, which is the point, and it means logging and test fixtures need to carry more of the diagnostic load.
Checking what you actually store
Before deciding, look. Pull the app container from a test device or simulator, open every database and preferences file, and list what is there. Most teams find at least one surprise: a full user object cached by a networking library, tokens in a preferences file, or search history nobody asked for.
An automated pass over the built artifact, such as PTKD.com, will flag the build-level signals around local storage, including backup settings, debuggable builds and storage APIs used for sensitive values, which is a quick starting inventory. The contents of the database at runtime still need a look on a real device, because they depend on how the app is used rather than on how it was built.
A short checklist
- Inventory every local database and what each column holds.
- Remove what does not need to be stored.
- Exclude sensitive files from backups where the platform allows it.
- Encrypt sensitive databases or fields with a random key held in the Keychain or Keystore.
- Plan the migration from the unencrypted version, including interrupted upgrades.
- Clear the database and the key on logout when the data belongs to the account, as covered in what to clear on logout.
What reviewers and customers expect to hear
When an enterprise customer or security reviewer asks about local storage, a short, specific answer works best: which data is stored on the device, which of it is encrypted beyond the platform default, which algorithm and library are used, where the key is held and when it is available, and what happens to the data on logout and uninstall. Teams that can answer those six points in a paragraph rarely get follow-up questions. Teams that answer with a general statement about encryption usually get a questionnaire.
It also helps to state what is deliberately not encrypted and why. A product catalogue cached for offline browsing does not need database encryption, and saying so shows the decision was made rather than missed.
Cross-platform frameworks
React Native and Flutter apps add a layer of choice, because their storage libraries vary widely. Plain key-value stores such as AsyncStorage write unencrypted files, while secure storage modules delegate to the Keychain and Keystore, and database plugins differ in whether they support SQLCipher. Check which library actually writes each piece of data, since a framework abstraction can hide a plain file underneath a reassuring name.
Whatever the framework, confirm the result on a device by opening the stored files directly rather than trusting the library's description.
What to take away
Platform encryption already protects a locked, powered-off phone, so database encryption is about the cases it misses: in-use, rooted, backed-up and shared devices. Decide by what a copy of the file would reveal, and first remove anything that does not need to be on the device. When encryption is warranted, SQLCipher for whole databases or field-level encryption for a few values both work, and either is only as strong as the key handling behind it. Generate the key randomly, keep it in the Keychain or Android Keystore, match its availability to when the app needs the data, and plan the migration before shipping it.



