Secure Android secrets with the system Keystore
Android apps routinely hold access tokens, refresh tokens and personal identifiers. The practical question behind how to use Android Keystore for securing secrets is not where to hide a password, but how to make decryption depend on a key the operating system will not export. Keystore provides that boundary for cryptographic keys, while encrypted values can live in normal app storage.
Android Keystore is a system service, not a password database. Applications create keys through the AndroidKeyStore provider and refer to them by alias. Depending on the handset, key material may be protected by a hardware-backed keystore, trusted execution environment or StrongBox security chip, so extraction is substantially harder than copying a file from internal storage.
Keep runtime encryption separate from the upload or app-signing key that identifies your publisher. Australian developers should also account for the Privacy Act 1988 and Australian Privacy Principles when an app handles personal information, while health and financial products may face additional state, sector or contractual obligations.
Know what Android Keystore can protect
The Keystore can hold AES encryption keys, asymmetric keys, certificates and related material. It cannot safely act as a general vault for every string an app receives. Store a value by encrypting it with a Keystore key and keep only the resulting ciphertext, IV and non-secret metadata in DataStore, preferences or a database.
Each app normally sees only keys belonging to its own UID, and standard AndroidKeyStore keys are non-exportable. That does not make every app secure: rooted devices, compromised application code and logs can still expose decrypted values. Treat Keystore as one layer in a threat model rather than permission to collect unnecessary data.
A secret delivered to a mobile client cannot remain genuinely secret. An APK can be inspected, and traffic can be intercepted if TLS is mishandled. Move privileged credentials to a backend, issue short-lived scoped tokens to the app and reserve Keystore for protecting local tokens or offline data.
Plan key storage and user authentication
Use distinct aliases for distinct purposes, such as a refresh token, a biometric credential and a database record. Generate random AES-256-GCM keys through KeyGenParameterSpec, let the provider create the IV for each encryption and avoid reusing IVs with the same key. Request StrongBox where supported and document the fallback path.
For high-value actions, setUserAuthenticationRequired and define a short authentication window. Android can require a fingerprint, face or device credential through the lock screen. In Australia, fingerprint and face unlock are familiar habits from banking, retail and transport apps, but enrolment changes or a new secure lock screen can invalidate authentication-bound keys on some devices. Handle that state deliberately.
Key settings worth documenting
- One alias per credential type and environment
- AES-256-GCM with a fresh provider-generated IV
- A documented authentication timeout
- A rotation owner and recovery plan
Encrypt a credential in Kotlin
The key-generation code stays small; the important design work happens before it. This example creates a non-exportable key when the alias does not already exist:
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
if (!keyStore.containsAlias("service-token")) {
val generator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
generator.init(
KeyGenParameterSpec.Builder(
"service-token",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
)
generator.generateKey()
}
To encrypt a token, load the key from KeyStore, create AES/GCM/NoPadding, initialise it for encryption and call updateAAD with a stable context such as the package name. Save the cipher IV and ciphertext as Base64 values. Do not generate a fixed IV, store the plaintext nearby or send either value to analytics.
Decryption retrieves the same key, initialises GCM with the saved 128-bit authentication tag length and repeats the AAD before doFinal. GCM rejects modified ciphertext, which helps detect tampering. Keep the alias stable across upgrades, use different keys for development and production builds, and avoid exposing secret values through saved-instance state, clipboard helpers or verbose logs.
Put encrypted values into the app
For a small token, store the ciphertext and IV in Preferences DataStore. For multiple records, put them in Room and keep the encryption boundary in the data-access layer. DataStore and Room provide persistence, not encryption by themselves, so the application must decrypt only at the point of use and clear temporary byte arrays where practical.
A DigitalOcean Droplet and its SSH-based phpMyAdmin setup belong to the server side of this architecture. The default installation notice behind this page is not a substitute for mobile key management. Protect the Droplet, database access and API proxy independently, and never distribute a database password or long-lived service credential through the Android app.
Keep implementation decisions together in the threat model, test notes and ownership register. The project workflow notes can provide one shared external reference when teams coordinate Android work with backend changes. Backend secrets should live in the server's secret-management path, while device-held values remain encrypted and revocable.
Test failures before release
Android Auto Backup creates a common failure mode: encrypted preferences may be restored to a new phone, but the Keystore key is not restored with them. The app then encounters InvalidKeyException or cannot decrypt the value. Exclude the relevant files from backup, or detect unusable restored ciphertext, request a fresh login and create a new local key without overwriting recoverable application state.
Test the complete credential lifecycle on devices used by Australian customers, including Samsung Galaxy handsets and Google Pixels sold here. Exercise app upgrades, uninstall and reinstall, offline launches, lock-screen changes and biometric re-enrolment. In busy Sydney and Melbourne, commuters often expect a cached session to work briefly after connectivity drops, so define expiry and refresh behaviour explicitly.
Release verification checklist
- Test hardware-backed and fallback keystore paths
- Exercise locks, credential changes and biometric re-enrolment
- Verify upgrade, reinstall and backup-restore behaviour
- Search logs, crash reports and databases for plaintext
Build a secure team workflow
Give each credential an owner, purpose, allowed environment and expiry. Review aliases during dependency and signing-key rotations, and plan how a compromised token can be revoked server-side. A release checklist should state whether a value may enter logs, whether Android backup may capture its ciphertext and which team can rotate it.
Use Australian privacy obligations as a minimum security signal, not as a substitute for technical controls. The Australian Signals Directorate's guidance and the Essential Eight can help structure cloud and application risk discussions, while PCI DSS or relevant state health rules may apply to particular products. Document which controls protect Australian personal information and how incidents will be escalated.
Consumer expectations matter too. Android users in Australia commonly rely on Google account backup and biometric unlock, but those conveniences should not silently restore an unusable secret. Provide a clear sign-in recovery path, revoke old sessions and avoid trapping users behind a credential that their restored device cannot open.
Open the Android project now, create one purpose-specific Keystore key, encrypt a token with AES-GCM and run the release checks on a real handset. Then publish the alias policy, backup decision and revocation owner to the team, so the next release starts with a verifiable secrets workflow rather than an improvised workaround.