Sparround

Secure storage: Keystore, EncryptedSharedPreferences

The Android Keystore is a hardware-protected store for cryptographic keys: key material never enters the app process, and operations (signing, encryption) run in a secure environment (TEE or a StrongBox chip). Keys are non-exportable — even on a rooted device the key itself cannot be stolen.

Key capabilities:

  • setUserAuthenticationRequired(true) — using the key demands biometrics/PIN
  • setInvalidatedByBiometricEnrollment(true) — enrolling a new fingerprint invalidates the key
  • StrongBox (on supporting devices) — a dedicated secure element chip

The practical layer: EncryptedSharedPreferences / EncryptedFile (Jetpack Security) — AES-256 encryption under a Keystore master key, with the familiar key-value interface.

What goes where: access/refresh tokens — encrypted storage; the PIN — never in plaintext, only salted-hashed or server-verified; the full card number (PAN) and CVV — never locally.

kotlin
class SecureTokenStore(context: Context) {

    private val masterKey = MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)   // açar Keystore-da
        .build()

    private val prefs = EncryptedSharedPreferences.create(
        context,
        "secure_tokens",
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

    fun saveRefreshToken(token: String) =
        prefs.edit().putString("refresh_token", token).apply()

    fun refreshToken(): String? = prefs.getString("refresh_token", null)

    fun clear() = prefs.edit().clear().apply()   // logout-da MÜTLƏQ
}

Storing tokens with EncryptedSharedPreferences

Interview traps: (1) "what if we keep the token in BuildConfig/strings.xml?" — APKs decompile; both are plaintext; (2) "SharedPreferences is sandboxed anyway, what's the problem?" — root, backup extraction and other vectors can read it; the sandbox is a security layer, not encryption; (3) without allowBackup="false" or dataExtractionRules, unencrypted files can reach cloud backups. The Jetpack Security library's deprecation may come up — the concept (Keystore master key + AES) stands regardless.

🛠 Practice task

Token storage drill:

  • Store a value in plain SharedPreferences, then read it back in plaintext with adb shell run-as <package> cat shared_prefs/*.xml.
  • Store the same value with EncryptedSharedPreferences and confirm the file is now unreadable.
  • Check allowBackup in the manifest and write the rule that excludes sensitive files from backups.

Done when: you can fill in the "what goes where" table for your own project.

📚 Sources and documentation