The Android Keystore holds keys, not data: an app has it generate a key under an alias and asks it to encrypt or decrypt, but never sees the key material, which on most phones lives in a Trusted Execution Environment or StrongBox chip. The iOS Keychain stores the small secrets themselves, released to the app according to an accessibility class. Either can demand a fingerprint, face or passcode first.
| Android Keystore | iOS Keychain | |
|---|---|---|
| Stores | Keys; your ciphertext lives in a normal file | The secret itself |
| Hardware | TEE or StrongBox, where present | Secure Enclave protects the keys |
| Access rules | KeyGenParameterSpec, e.g. user authentication required | Accessibility, e.g. WhenUnlocked |
| Biometric binding | setUserAuthenticationRequired(true, ...) | Access control such as .biometryCurrentSet |
A WebView reaches neither, so secure storage always means a plugin. On Android the usual pattern, used by both plugins in the next section, is an AES-GCM key generated in the Keystore that encrypts values which are then written to an ordinary SharedPreferences file. Google's own wrapper for this, EncryptedSharedPreferences in androidx.security:security-crypto, is now deprecated: version 1.1.0-beta01 (June 2025) deprecated all its APIs "in favour of existing platform APIs and direct use of Android Keystore", so prefer plugins that call the Keystore directly.