Securely Store Sensitive Data on Android with EncryptedSharedPreferences and Keystore

10 min read Learn step‑by‑step how to protect sensitive data on Android using EncryptedSharedPreferences backed by the Android Keystore, with code examples and troubleshooting tips. October 11, 2026 05:00 How to Securely Store Data on Android Using EncryptedSharedPreferences & Keystore

Why plain SharedPreferences aren’t enough

Android’s SharedPreferences is convenient for persisting small key‑value pairs, but the data is stored as clear‑text XML in the app’s internal storage. If a device is rooted, backed up, or extracted via forensic tools, anyone with file‑system access can read those values – a serious risk for passwords, API tokens, or personal identifiers.

The Android Keystore system provides hardware‑backed or software‑backed key storage that never leaves the secure enclave in a readable form. By combining the Keystore with EncryptedSharedPreferences, you get a drop‑in replacement for SharedPreferences that encrypts keys and values automatically.

Before you start

  • Android Studio (Arctic Fox or newer) installed on your workstation.
  • Device running Android 6.0 (Marshmallow, API 23) or later – the Keystore API is available from API 23, and EncryptedSharedPreferences is part of AndroidX Security library, which works back to API 21.
  • Enable Developer options and USB debugging on the test device.
  • Make sure the device is charged or connected to power; the first run may generate a hardware‑backed key, which can take a few seconds.

Step 1 – Add the AndroidX Security library

Open your app module build.gradle (or build.gradle.kts) and add the Security dependency:

dependencies {
    implementation "androidx.security:security-crypto:1.1.0-alpha03"
}

Sync the project. The library bundles EncryptedSharedPreferences and the underlying MasterKey helper class.

Step 2 – Create a MasterKey

The MasterKey is a symmetric AES‑256 key generated and stored by the Keystore. It will be used to encrypt/decrypt the SharedPreferences file.

Place the following Kotlin code in a singleton or a utility object, e.g., SecurePrefs.kt:

import android.content.Context
import androidx.security.crypto.MasterKey
import androidx.security.crypto.EncryptedSharedPreferences

object SecurePrefs {
    private const val PREF_FILE = "secure_prefs"

    fun getEncryptedPrefs(context: Context): EncryptedSharedPreferences {
        // Build or retrieve the MasterKey. The default configuration uses AES256_GCM.
        val masterKey = MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build()

        return EncryptedSharedPreferences.create(
            context,
            PREF_FILE,
            masterKey,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        ) as EncryptedSharedPreferences
    }
}

Explanation:

  • MasterKey.Builder creates a key that lives inside the Keystore. The key never leaves the secure hardware (or a protected software store) in clear form.
  • PrefKeyEncryptionScheme.AES256_SIV encrypts the preference names, preventing an attacker from learning the keys even if they can read the file.
  • PrefValueEncryptionScheme.AES256_GCM encrypts the values with authenticated encryption, ensuring integrity.

Step 3 – Read and write data

Using the helper you just created, reading and writing is identical to normal SharedPreferences, except the object type is EncryptedSharedPreferences.

// Example usage inside an Activity or ViewModel
val encryptedPrefs = SecurePrefs.getEncryptedPrefs(this)

// Store a secret token
encryptedPrefs.edit().putString("auth_token", "my‑super‑secret-token").apply()

// Retrieve the token later
val token = encryptedPrefs.getString("auth_token", null)

After the .apply() call, the data is written to /data/data/your.package.name/shared_prefs/secure_prefs.xml, but the file contents are fully encrypted. Opening the file with a text editor will show only gibberish.

Step 4 – Verify encryption on the device

To convince yourself that the data is really encrypted, you can use adb shell to cat the file:

adb shell "run-as com.example.myapp cat /data/data/com.example.myapp/shared_prefs/secure_prefs.xml"

You should see a block of seemingly random characters wrapped in XML tags – no plain‑text token.

Step 5 – Handle device‑specific quirks

Most modern devices support hardware‑backed Keystore keys. However, on some low‑end or heavily customized ROMs the Keystore may fall back to a software implementation, which is still secure but may be slower. The AndroidX library abstracts this away, but you may notice a small delay the first time a key is generated.

If your app targets API 31+ and you want to enforce hardware‑backed keys, you can add a check:

if (masterKey.isUserAuthenticationRequired) {
    // Hardware‑backed key is present
} else {
    // Fallback – consider showing a warning or disabling high‑risk features
}

Step 6 – Migrate existing plain preferences (optional)

Many apps start with plain SharedPreferences and later switch to encrypted storage. A safe migration path is:

  1. Read the old value using the legacy SharedPreferences.
  2. Write it into EncryptedSharedPreferences using the same key.
  3. Delete the old entry from the plain file.
val legacyPrefs = context.getSharedPreferences("legacy_prefs", Context.MODE_PRIVATE)
val token = legacyPrefs.getString("auth_token", null)
if (token != null) {
    encryptedPrefs.edit().putString("auth_token", token).apply()
    legacyPrefs.edit().remove("auth_token").apply()
}

Troubleshooting

  • "KeyStoreException: AndroidKeyStore not available": This occurs on emulators that lack a Keystore implementation. Use a physical device or enable the "Google APIs" system image which includes a software Keystore.
  • App crashes on first launch: Ensure you are not calling SecurePrefs.getEncryptedPrefs() from a background thread before the app’s Application context is fully initialized. The Keystore initialization must happen on the main thread.
  • Decryption fails after a device reset: The MasterKey is tied to the device’s secure hardware. If the user performs a factory reset, the key is lost and all encrypted data becomes unrecoverable. Inform users that a reset will erase stored secrets.

Best‑practice checklist

  • Never store raw passwords or private keys directly; always encrypt them.
  • Prefer .apply() over .commit() for async writes, unless you need immediate persistence.
  • Limit the amount of data stored in EncryptedSharedPreferences. For larger blobs, consider using EncryptedFile or a database with SQLCipher.
  • When using biometric authentication, combine setUserAuthenticationRequired(true) on the MasterKey builder to require a fingerprint or device credential before decrypting.

Advanced: Adding biometric unlock (optional)

If you want the secret data to be accessible only after the user authenticates with biometrics, extend the MasterKey configuration:

val biometricKey = MasterKey.Builder(context)
      .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
      .setUserAuthenticationRequired(true, 5_000) // 5‑second timeout
      .build()

  val securePrefs = EncryptedSharedPreferences.create(
      context,
      "biometric_prefs",
      biometricKey,
      EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
      EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
  )

Now any attempt to read or write will prompt the user for a fingerprint, face unlock, or device PIN, depending on the device’s configuration.

Wrapping up

By following these steps you have replaced insecure plain preferences with a robust, Android‑native encryption solution that leverages the device’s Keystore. The approach works on the vast majority of Android phones, requires no root, and adds only a negligible performance overhead. Remember to handle edge cases—factory resets, device‑specific Keystore quirks, and migration from legacy data—to keep the user experience smooth.

Secure storage is a foundational piece of any privacy‑focused app. With EncryptedSharedPreferences and the Keystore, you can confidently store tokens, API keys, and other secrets without exposing them to the wide range of attacks that target plain files.

User Comments (0)

Add Comment
We'll never share your email with anyone else.