To fix insecure data storage in Android, work in a clear order: do not store sensitive data on the device at all when you can keep it server-side, and for anything you must persist, encrypt it with a key held in the Android Keystore and write it to app-internal storage rather than external storage. Plain SharedPreferences saves values as cleartext, so it is the wrong place for tokens or personal data. EncryptedSharedPreferences used to be the drop-in encrypted replacement, but the Jetpack Security Crypto library that provides it is now deprecated, so new code should use a Keystore-backed approach such as Jetpack DataStore combined with Tink, or your own Keystore encryption. Then keep secrets out of logs, cache, and backups.
Short answer
Insecure data storage means sensitive data sits somewhere on the device that another app, a rooted or stolen phone, or a backup can read, and the fix is to store less and encrypt the rest. Per Android's security guidance, internal storage is private to your app by default and is the right place for sensitive files, while external storage is shared and should never hold secrets. For key-value data, plain SharedPreferences is cleartext; EncryptedSharedPreferences added Keystore-backed encryption but its library is now deprecated, so use DataStore with Tink or direct Keystore encryption instead. This maps to the storage requirements in OWASP MASVS.
What insecure data storage means
Insecure data storage is the class of bug where an app keeps sensitive information in a place or a form that is not protected against someone who can reach the device's files. OWASP ranks it among the top mobile risks because it is common and quiet: the app works perfectly, but a token, password, or piece of personal data is sitting in cleartext where it can be recovered. The threat is not only a thief holding a stolen phone; it is also a rooted device, a malicious app with the right access, a device backup, and forensic extraction.
The reason it happens so often is that the convenient storage options are plaintext by default. Writing a value to SharedPreferences or a SQLite row takes one line and stores it readable, so the easy path and the insecure path are the same path. Fixing it means treating on-device storage as untrusted ground: assume anything you write could be read by someone who should not, and decide, for each piece of data, whether it belongs on the device at all and, if so, in what encrypted form.
Where Android apps leak data
Most on-device leaks come from a short list of predictable places, and knowing them tells you where to look. SharedPreferences stores its data as a plaintext XML file in your app's internal directory. A SQLite database stores rows in cleartext unless you encrypt it. External storage is shared across the device and is the wrong home for anything private. Application logs often capture tokens or personal data that were passed through them. The system cache and temporary files can retain sensitive content. And automatic backups can copy your files off the device.
None of these is malicious on its own; each is just unprotected by default. The pattern is that data written for convenience ends up in a readable file, and the file outlives the moment it was needed, in a log, a cache, or a backup archive. So the first move in a fix is an inventory: list every place your app writes data, mark which entries are sensitive, and you will usually find secrets in one of these predictable locations rather than somewhere exotic.
Shared Preferences: why plain ones are insecure
Plain SharedPreferences is insecure for secrets because it stores values as cleartext, so anyone who can read your app's files can read the values. The data lives in an XML file inside your app's internal storage, which is private on a healthy device, but that privacy evaporates on a rooted device, through a backup that includes the file, or via any path that grants file access. A session token or password written to SharedPreferences is therefore recoverable without breaking any encryption, because there is none.
This does not make SharedPreferences wrong for everything. It is fine for non-sensitive settings such as a theme choice or a last-opened tab, where disclosure causes no harm. The mistake is using it for tokens, credentials, keys, or personal data, because those are exactly the values an attacker wants and exactly the values it stores in the clear. So the rule is simple: keep non-sensitive preferences in SharedPreferences if you like, and move anything sensitive to an encrypted store.
EncryptedSharedPreferences and its deprecation
EncryptedSharedPreferences was the standard answer to that problem: it wrapped the same key-value API but encrypted both keys and values using a master key held in the Android Keystore, so the data on disk was ciphertext and the encryption key never lived in your code. For years it was the recommended drop-in fix for sensitive key-value storage, and it still functions where it is already in use.
The important update is that the Jetpack Security Crypto library providing EncryptedSharedPreferences and EncryptedFile is now deprecated, so it is not the choice for new code. Google's current direction for local persistence is Jetpack DataStore, which is not encrypted on its own, combined with an encryption library such as Google Tink, with keys protected by the Android Keystore. The Keystore remains the anchor in every version of the answer: it holds the key in hardware-backed storage where your app can use it but cannot extract it. So for new work, encrypt with a Keystore-backed key through DataStore plus Tink or your own encryption, and reserve EncryptedSharedPreferences for code that already depends on it.
The storage fix by data type
Matching each kind of data to the right home and protection is the core of the fix. The table below maps it.
| Data type | Store it where | Protection |
|---|---|---|
| Session token, credential | Prefer server-side or in-memory; else encrypted internal storage | Keystore-backed encryption |
| Personal data (PII) | App-internal storage, encrypted | Keystore-backed encryption |
| Structured records | Encrypted database in internal storage | Encrypted DB plus Keystore key |
| Non-sensitive settings | SharedPreferences or DataStore | None needed |
| Large shared media | Scoped or external storage | Never store secrets here |
Read the top rows as the priority: the best protection for a secret is not storing it on the device, and encryption with a Keystore key is the fallback when you must.
Practical steps to remediate
Start by inventorying every place the app writes data and marking what is sensitive, then remove what you do not need, since data you never store cannot leak. For secrets you can avoid persisting, keep them server-side and fetch on demand, or hold short-lived values in memory only. For sensitive data that genuinely must live on the device, encrypt it with a key from the Android Keystore and write it to internal storage, using DataStore with Tink or an equivalent Keystore-backed approach rather than the deprecated library.
Then close the secondary leaks. Move any sensitive files off external storage into internal storage. Strip tokens and personal data out of log statements, especially in release builds. Exclude sensitive files from automatic backup so a backup archive does not carry your secrets off the device. Clear temporary and cache copies of sensitive content when you are done with them. Each step targets one of the predictable leak locations, and together they turn on-device storage from readable to protected.
How to test your own storage responsibly
On your own app and a device or emulator you control, you can confirm what is exposed by looking at where your app writes. Using a debuggable build, inspect your app's internal directory, the shared_prefs XML files and the databases folder, and open them to see whether sensitive values are sitting in cleartext. Check whether anything sensitive is written to external storage or appears in logcat while you exercise the app. A static analysis tool such as MobSF will flag plaintext storage, world-readable files, and an enabled backup flag for you.
Keep this to apps and devices you own or are authorized to assess, which is the boundary for responsible testing. The goal is to see your data the way someone with file access would, so you know which values need encrypting and which files need to move or be excluded from backup. If you can read a token or personal record in a preferences file or a database, that is the finding, and the fix is to encrypt it with a Keystore key or stop storing it.
Remediation checklist
Working through these steps closes the common storage gaps. The checklist below covers them.
| Step | Action | Done? |
|---|---|---|
| Inventory storage | List every place the app writes data | [ ] |
| Store less | Remove secrets you do not need on-device | [ ] |
| Encrypt what remains | Use a Keystore-backed key, not plain SharedPreferences | [ ] |
| Use internal storage | Keep sensitive files off external storage | [ ] |
| Exclude from backup | Stop sensitive files copying into backups | [ ] |
| Clean logs and cache | Strip secrets from logs and temporary files | [ ] |
The step most often skipped is excluding sensitive files from backup, because the data looks safe on the device while a backup quietly carries it off.
Where a scan fits
Once you have encrypted and relocated your sensitive data, checking the whole app for the same pattern is worthwhile, since plaintext storage rarely appears in only one place.
A scanner like PTKD.com analyzes your build and flags issues such as sensitive values stored in cleartext, hardcoded keys, an enabled backup flag on sensitive data, and over-broad permissions by severity, mapped to OWASP MASVS. To be clear about the boundary: PTKD reports these findings for you to fix; it does not encrypt your data or move your files. It points at the insecure storage so you can protect each item.
What to take away
- Fix insecure data storage by storing less first: keep sensitive data server-side or in memory when you can, so there is nothing on the device to leak.
- Plain SharedPreferences and unencrypted databases store cleartext, so they are the wrong home for tokens, credentials, or personal data.
- EncryptedSharedPreferences added Keystore-backed encryption, but its Jetpack Security Crypto library is deprecated, so new code should use DataStore with Tink or direct Keystore encryption.
- Keep sensitive files in internal storage, exclude them from automatic backup, and strip secrets from logs and cache.
- Test by inspecting your own app's stored files, and a tool like PTKD.com helps flag plaintext storage across the app.



