Android Keystore Security Review Checklist
Android Keystore lets an application generate and use keys through Android’s keystore provider. Whether a key is hardware-backed depends on device and configuration; an app must not claim every key has same protection. Key material is normally not exported for Keystore keys, but application code can still misuse a key, expose plaintext, retain ciphertext indefinitely, or lose access after lifecycle changes. Review key purpose and data lifecycle together.
The practical target is a release artifact, not a Kotlin snippet. Capture application ID, version code, signing certificate digest, Android version, device model, test date, and evaluator before recording result. Locate every alias, caller, transformation, and persistent ciphertext format. An alias is an identifier, not secret storage: keep it non-sensitive and stable enough for documented migration.
Generate only intended capability
KeyGenParameterSpec.Builder defines authorization when generating Android Keystore keys. Limit purposes with KeyProperties.PURPOSE_ENCRYPT and PURPOSE_DECRYPT for data encryption, or signing purposes where signing is required. Do not grant encryption, decryption, signing, and verification to one alias for convenience. Match block modes and paddings to transformation used by application code; incompatible settings must fail test rather than silently fall back.
val spec = KeyGenParameterSpec.Builder(
"profile-wrap-v2",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(
0,
KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
)
.build()
This example makes each cryptographic use require an allowed user authentication event. It does not authenticate remote user identity or authorize server operations. For a sensitive local unlock, use a BiometricPrompt crypto object with an authentication-bound key, then attempt operation before prompt, after success, after cancel, and after app backgrounding. Record expected exceptions and user-visible recovery behavior. Do not replace this with a Boolean “biometric passed” flag; binding the crypto operation to prompt result matters.
Authentication type, validity duration, and invalidation behavior are product decisions. A per-use key is unsuitable for background sync; a timed window may be unsuitable for a high-impact action. Document why selected setUserAuthenticationParameters value fits action. When biometric enrollment changes, test whether chosen settings invalidate applicable keys and whether app can recover without data loss. Never clear ciphertext blindly: recovery may need server re-wrap, logged-in reauthentication, or a data-loss notice.
StrongBox is request, not guarantee
On compatible devices, setIsStrongBoxBacked(true) requests StrongBox-backed key generation. It can fail when StrongBox is unavailable or lacks requested algorithm, size, mode, or padding support. Treat absence as a tested capability branch, not a catch-all that silently downgrades without a decision. Capture KeyInfo or platform behavior, model, API level, and generated-key result for each target device class. If threat model requires StrongBox, unsupported devices fail eligibility or use an approved alternative; do not describe software-backed fallback as StrongBox protection.
Evidence and acceptance record
| Check | Completed artifact | Owner | Pass acceptance |
|---|---|---|---|
| Alias inventory | Alias, use site, purpose, data classification table. | Android lead | Every production key has known owner and purpose. |
| Key generation | Source reference, generated spec, test build identifier. | Crypto owner | Keystore creates key with no hard-coded key bytes. |
| Authentication binding | Prompt test recording and success/cancel/background results. | Mobile security owner | Protected crypto use fails until required authentication. |
| StrongBox branch | Device matrix and failure or success result. | Device compatibility owner | Requirement or fallback decision is explicit and tested. |
| Ciphertext lifecycle | Versioned envelope sample, reinstall/restore/rotation tests. | Data owner | Old and new data follow documented recovery path. |
| Release inspection | Decompilation notes and log review with test markers. | Release owner | No alias, plaintext, key bytes, or sensitive metadata leaks. |
Mark pass only when artifact and acceptance exist. Mark fail when a key is exported, unprotected use succeeds, wrong purpose works, sensitive key material is hard-coded, or documented lifecycle breaks. Mark not verified when device, build, or access is missing.
Exceptions and remediation
Each exception states affected alias, data class, MASVS relation, risk owner, compensating control, expiry, and approval. A device limitation without owner and expiry is not closure. Prefer remediation in this order: generate dedicated key; narrow purposes and cryptographic parameters; bind high-risk use to authentication; version ciphertext; add tested key rotation or recovery; remove unsafe logs. Rebuild, rerun same test set, and attach retest evidence before closing.
Android documentation and OWASP MASTG are technical references, not certification. Source-code review or mobile application testing can examine agreed scope.
Worked key test and closure
Create a release-like key alias with purpose, algorithm, and user-auth requirement documented. On device, record alias metadata without exporting key material, then attempt operation before and after device authentication. Expected output is successful cryptographic operation only within configured authorization window; outside it, application handles UserNotAuthenticatedException without weakening policy or logging secret material. Verify backup and migration behavior: wrapped application data may move, but non-exportable Keystore key normally remains device-bound unless design explicitly provides controlled recovery.
Edge case: device hardware can differ. A key marked hardware-backed on one device may fall back to a different implementation on another. Record attestation or KeyInfo evidence where design relies on hardware property, and treat unavailable property as a design decision, not a silent pass. Never use key alias as authorization claim; server still authorizes user and transaction.
Close with build identifier, alias configuration extract, tested device/API, expected success and denial outputs, recovery decision, and owner. Reopen after algorithm change, Android target update, biometric policy change, or encrypted-data format migration.
Reproduce key-use result
Generate key on physical test device, record alias only, then encrypt fixed synthetic plaintext and persist ciphertext envelope with algorithm, IV, and version. Expected result: Keystore operation succeeds after required authentication and caller receives ciphertext, never raw key bytes. Attempt same cipher initialization before prompt, after cancellation, and after required invalidation event; expected result is authentication or key-invalidated failure handled by recovery path. Capture exception class, API level, device model, build ID, owner, pass/fail, and remediation ticket.