On this page
Encryption is only as strong as the protection of its key. Store an AES key in a file next to the encrypted data, and anyone who copies both has everything. The Android Keystore solves key storage: your app gets a handle to perform operations, while the key itself stays in a protected environment.
How the Keystore works
- Non-exportable keys: key material can't be extracted from the Keystore.
- Hardware-backed on most modern devices, via a Trusted Execution Environment (TEE). Some devices also have StrongBox, a dedicated secure element.
- Key restrictions: algorithms, purposes, block modes, validity, and user-authentication requirements are enforced by the Keystore.
Generate an AES key and encrypt data
object KeystoreCrypto {
private const val KEYSTORE = "AndroidKeyStore"
private const val ALIAS = "notes_key"
private const val TRANSFORMATION = "AES/GCM/NoPadding"
private fun getOrCreateKey(): SecretKey {
val ks = KeyStore.getInstance(KEYSTORE).apply { load(null) }
(ks.getEntry(ALIAS, null) as? KeyStore.SecretKeyEntry)?.let { return it.secretKey }
val spec = KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE)
.apply { init(spec) }
.generateKey()
}
/** Returns IV + ciphertext. A fresh random IV is generated for every encryption. */
fun encrypt(plain: ByteArray): ByteArray {
val cipher = Cipher.getInstance(TRANSFORMATION).apply { init(Cipher.ENCRYPT_MODE, getOrCreateKey()) }
return cipher.iv + cipher.doFinal(plain) // GCM IV is 12 bytes
}
fun decrypt(data: ByteArray): ByteArray {
val iv = data.copyOfRange(0, 12)
val cipher = Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.DECRYPT_MODE, getOrCreateKey(), GCMParameterSpec(128, iv))
}
return cipher.doFinal(data, 12, data.size - 12)
}
}AES-GCM provides confidentiality and integrity: tampered ciphertext fails to decrypt. Never reuse an IV with the same key. Let the cipher generate it, as above. For most apps, a higher-level library such as Tink, with a Keystore-wrapped master key, is easier to use correctly. See Encrypted Data Storage.
Keys that require user authentication
val spec = KeyGenParameterSpec.Builder("payment_auth_key", KeyProperties.PURPOSE_SIGN)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG) // auth for every use
.setInvalidatedByBiometricEnrollment(true) // new fingerprint โ key invalid
.build()Using this key requires a successful BiometricPrompt with a CryptoObject. That's much stronger than "biometric check passed, so continue". See Biometric Authentication.
StrongBox and attestation
setIsStrongBoxBacked(true)requests a StrongBox key on supported devices. HandleStrongBoxUnavailableExceptionand fall back to a TEE key.- Key attestation lets your server verify, via a certificate chain, that a key was generated in secure hardware on a genuine device. It's useful for high-assurance scenarios, and verification belongs on the server.
Pitfalls
| Pitfall | Consequence | Guard |
|---|---|---|
| Keys can be invalidated (lock screen removed, biometric changes) | KeyPermanentlyInvalidatedException | Catch, delete the key, re-create it, and re-authenticate or re-fetch data |
| Keys aren't backed up | Restored encrypted data can't be decrypted | Exclude encrypted data from backup, or treat it as re-fetchable |
| Uninstall deletes keys | Data unrecoverable | Design encrypted local data as a cache, or sync to the server |
| Rolling your own crypto modes | Weak security | Use AES-GCM via the Keystore or Tink; never ECB |
Use cases
Encrypting tokens and local secrets
A master key in the Keystore wraps the data encryption keys.
Biometric-confirmed transactions
An auth-bound signing key signs a server challenge, and the server verifies it with the public key.
Device binding
An attested key pair proves requests come from the enrolled device.
Frequently asked questions
Can I store a password or token directly in the Keystore?
The Keystore stores keys, not arbitrary values. Encrypt the token with a Keystore key and store the ciphertext.
Is the Keystore safe on rooted devices?
Hardware-backed keys resist extraction even on rooted devices, but an attacker with root can still use the key through your app while it runs. Combine with server-side checks.
Does every device have StrongBox?
No. It's optional hardware. Always handle its absence.
Conclusion
The Android Keystore keeps key material out of your app's reach, often in secure hardware, with enforced restrictions and optional biometric binding and attestation. Use it (directly or via Tink) for every key that protects sensitive data, and handle invalidation gracefully.
Next step: decide what never belongs in the app at all with Secure API Keys and Secrets.
Official references
๐ฑ Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
NotiVaultNotification history: recover dismissed notificationsGet it โธ
Walzi4K wallpapers for lock and home screenGet it โธ
Ready to build real Android applications?
Security is easiest to get right when it is part of how you build from the first screen. Strengthen the Android fundamentals that secure, privacy-respecting apps are built on.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.