Skip to main content

Secure storage and Android Keystore

Android Keystore stores cryptographic keys so key material can remain non-exportable and may be hardware-backed. It does not store arbitrary large secrets, make a compromised process trustworthy, or replace server authorization.

Envelope pattern​

Keystore key (non-exportable where supported)
↓ encrypt/decrypt
small data-encryption key or app secret
↓
encrypted file/database values

For many applications, use a maintained high-level cryptography library rather than inventing this envelope yourself. The design still must define backup, invalidation, rotation, logout, and device migration.

Generate an AES key​

val generator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore",
)
generator.init(
KeyGenParameterSpec.Builder(
"account-cache-v2",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT,
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
)
val key = generator.generateKey()

Use a random IV generated for each GCM encryption; store it with version/ciphertext/tag. Never reuse a nonce/IV with the same GCM key.

User authentication binding​

Keys can require recent biometric/device-credential authentication. This improves protection for actions like opening a vault, but creates UX/lifecycle consequences:

  • enrollment/lock-screen changes can invalidate keys depending on configuration;
  • authentication windows expire;
  • background jobs cannot show biometric prompts;
  • process death can interrupt crypto operation;
  • biometric authenticates the local device user, not their server role or transaction details.

Hardware-backed is conditional​

Inspect key security level/attestation when your threat model needs it; support devices where hardware backing is unavailable or decide an explicit product restriction. Do not claim hardware protection merely because the key alias is in Android Keystore.

Failure and recovery are mandatory​

Handle missing alias, invalidated key, corrupt/truncated ciphertext, authentication required, OS/device migration, backup restored without key, and logout/account switch. Often encrypted local caches should be deleted and re-fetched rather than trapping the user permanently.

The app can use the key

Malware/instrumentation executing inside a compromised app process may ask Keystore to perform allowed operations even without exporting key bytes. Keystore raises extraction difficulty; it does not make runtime compromise harmless.

What not to store​

  • ❌ API secrets that must remain secret from every APK user.
  • ❌ server private keys or service-account credentials.
  • ❌ a hardcoded “encryption key” split across Kotlin strings.
  • ✅ public API identifiers restricted by package/signature where supported.
  • ✅ per-installation/device keys and appropriately scoped cached credentials, with server-side revocation.

Sources​

Interview question​

Does Keystore protect a refresh token on a rooted device? It can make key extraction harder, especially with hardware backing, but runtime hooks may capture plaintext before/after encryption or invoke permitted operations. Use short scopes, rotation/revocation, server risk controls, and minimize local lifetime.