Setting up Keystore for Secure Data Storage in Android
Android Keystore System is not just a place to store keys. It's a hardware-isolated container (Trusted Execution Environment or Secure Element) from which the private key never leaves the device as plaintext. Crypto operations execute inside TEE. Most Android apps don't use this, storing tokens in plaintext SharedPreferences.
How to Correctly Generate and Use Keys
AES key generation in Keystore:
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
keyGenerator.init(
KeyGenParameterSpec.Builder(
"my_secure_key_alias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(false) // true for biometry
.setKeySize(256)
.build()
)
keyGenerator.generateKey()
After this, the key lives in Keystore. Encrypt data via Cipher with this key, save the encrypted blob + IV in SharedPreferences or Room. The key is not directly accessible from Keystore — you can't export bytes, only use it for operations via JCE API.
Why GCM, not CBC. AES-GCM provides authenticated encryption: on decryption, MAC is verified, and if data is modified, Cipher.doFinal() throws AEADBadTagException. AES-CBC doesn't — corrupted data decrypts to garbage without error.
StrongBox vs TEE
On devices with Android 9+ and hardware Secure Element, StrongBox Keymaster is available. Difference from regular TEE: keys stored in physically separate chip (Pixel 3+, Samsung Galaxy S series). Enabled via flag:
.setIsStrongBoxBacked(true)
StrongBox is slower (~100ms per operation vs ~5ms for TEE), but keys survive even if main processor is compromised. For most apps, TEE is sufficient. StrongBox makes sense for critical transaction signing keys (fintech, healthcare).
Biometric Key Protection
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(
0, // 0 = every time, >0 = timeout in seconds
KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
)
AUTH_BIOMETRIC_STRONG on Android 11+ means only Class 3 biometry (Face ID level or fingerprint sensor passing CDD certification). Class 2 (most front cameras without dedicated secure enclave) doesn't qualify. When setUserAuthenticationRequired(true), attempting to decrypt without prior biometry throws UserNotAuthenticatedException.
Perform biometric authentication via BiometricPrompt.CryptoObject(cipher) — this links the biometric session with your specific Cipher, initialized with your key. Important: without CryptoObject, biometry doesn't authorize key use.
Key Invalidation on Biometry Change
.setInvalidatedByBiometricEnrollment(true)
Default true — key invalidates when new fingerprint is added or biometry reset. Correct behavior for data encryption keys. Handle KeyPermanentlyInvalidatedException when using invalidated key — generate new key and ask user to login again.
Process
Audit — find everything stored in plaintext SharedPreferences (tokens, cookies, session IDs). Design schema: which data to encrypt with Keystore key, which requires biometry. Implement CryptoManager with encrypt/decrypt, integrate with existing storage layer (DataStore, Room). Test on emulators API 23–34 and real devices — Keystore behavior on some custom ROMs (Huawei without GMS) differs.
Timeline: 1–3 days depending on data volume and biometric protection requirements.







