Secure Key Storage in StrongBox/TEE (Android) for Crypto Wallet
Android KeyStore exists since API 18, but hardware security appeared later and remains heterogeneous. On one device, keys stored in StrongBox — dedicated security chip (Apple SE analogue). On another — only TEE (Trusted Execution Environment) as isolated process on same SoC. On third (budget devices) — only software-backed KeyStore. Wallet must account for this.
How to Check Key Protection Level
After creation, can't assume key in StrongBox. KeyInfo shows actual level:
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val keyEntry = keyStore.getEntry("wallet-key", null) as KeyStore.PrivateKeyEntry
val keyFactory = KeyFactory.getInstance(keyEntry.privateKey.algorithm, "AndroidKeyStore")
val keyInfo = keyFactory.getKeySpec(keyEntry.privateKey, KeyInfo::class.java)
val securityLevel = when {
keyInfo.securityLevel == KeyProperties.SECURITY_LEVEL_STRONGBOX -> "StrongBox"
keyInfo.securityLevel == KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT -> "TEE"
else -> "Software"
}
KeyInfo.securityLevel appeared in API 31. Before — KeyInfo.isInsideSecureHardware(), which doesn't distinguish StrongBox and TEE. For production wallet: require StrongBox on API 28+ devices (Android 9+), on others — TEE minimum, with explicit user warning.
Creating Key with StrongBox Requirement
val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
"AndroidKeyStore"
)
val paramSpec = KeyGenParameterSpec.Builder(
"wallet-signing-key-v1",
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)
.setIsStrongBoxBacked(true) // require StrongBox
.build()
try {
keyPairGenerator.initialize(paramSpec)
keyPairGenerator.generateKeyPair()
} catch (e: StrongBoxUnavailableException) {
// StrongBox unavailable — fallback to TEE or inform user
retryWithoutStrongBox()
}
StrongBoxUnavailableException must be handled explicitly — don't silently fail. Fallback logic: retry without setIsStrongBoxBacked(false), then check KeyInfo.securityLevel, then decide whether to display warning.
Android vs iOS: Fundamental Difference
iOS Secure Enclave supports only P-256. Android StrongBox supports P-256 and RSA, but not secp256k1. Same situation: for ETH/BTC private keys need wrapper.
Scheme analogous to iOS: Android KeyStore P-256 key encrypts secp256k1 key via Cipher with ECDH + AES-GCM algorithm. Encrypted blob — in EncryptedSharedPreferences or Room with encryption.
But there's nuance: KeyAgreement (ECDH) with KeyStore key works without biometric confirmation if setUserAuthenticationRequired not set. For decrypt operations (accessing ETH key before transaction signing), explicitly require auth at moment of use — via setUnlockedDeviceRequired(true) + setUserAuthenticationParameters.
Test on Real Hardware
StrongBox unavailable in emulator. Test on Pixel 3+ (StrongBox with API 28), Samsung Galaxy S10+ (Samsung Knox as separate SE), and budget devices without StrongBox — ensure fallback correct.
Timeline — 3–5 days: creating keys, encryption scheme for secp256k1, handling fallback by security levels, real device testing.







