Реалізація безпечного зберігання ключів у StrongBox/TEE (Android) для криптокошелька
Android KeyStore існує з API 18, але апаратна безпека з'явилася пізніше і до сих пір неоднорідна. На одному пристрої ключі зберігаються у StrongBox — виділеному security chip (аналог Apple SE). На іншому — тільки TEE (Trusted Execution Environment) як ізольований процес на тому ж SoC. На третьому (дешеві пристрої) — тільки software-backed KeyStore. Кошелек повинен це враховувати.
Як перевірити рівень захисту ключа
Після створення не можна просто припустити, що ключ у StrongBox. KeyInfo показує реальний рівень:
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 з'явилась в API 31. До цього — KeyInfo.isInsideSecureHardware(), яка не розрізняє StrongBox та TEE. Для production-кошелька: вимагайте StrongBox на пристроях з API 28+ (Android 9+), на інших — TEE як мінімум, з явним попередженням користувачу.
Створення ключа з вимогою StrongBox
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) // вимагаємо StrongBox
.build()
try {
keyPairGenerator.initialize(paramSpec)
keyPairGenerator.generateKeyPair()
} catch (e: StrongBoxUnavailableException) {
// StrongBox недоступен — fallback на TEE або інформуємо користувача
retryWithoutStrongBox()
}
StrongBoxUnavailableException потрібно обробляти явно — не молча падати. Fallback-логіка: спроба з setIsStrongBoxBacked(false), потім перевірка KeyInfo.securityLevel, потім рішення відображати ли попередження.
Android проти iOS: принципіальне розрізнення
На iOS Secure Enclave підтримує тільки P-256. На Android StrongBox підтримує P-256 та RSA, але не secp256k1. Ситуація та ж: для приватних ключів ETH/BTC потрібна обгортка.
Схема аналогічна iOS: Android KeyStore P-256 ключ використовується для шифрування secp256k1 ключа через Cipher з алгоритмом ECDH + AES-GCM. Зашифрований blob — у EncryptedSharedPreferences або Room з шифруванням.
Але є нюанс: KeyAgreement (ECDH) з KeyStore-ключем працює без біометричного підтвердження, якщо не встановлено setUserAuthenticationRequired. Для операцій розшифровки (доступ до ETH-ключа перед підписанням транзакції) потрібно явно вимагати аутентифікацію в момент використання — через setUnlockedDeviceRequired(true) + setUserAuthenticationParameters.
Перевіряємо на реальному залізі
StrongBox в емуляторі недоступен. Тестуємо на Pixel 3+ (StrongBox з API 28), Samsung Galaxy S10+ (Samsung Knox як окремий SE), та на бюджетних пристроях без StrongBox — переконуємось, що fallback коректний.
Часова шкала — 3–5 днів: створення ключів, схема шифрування для secp256k1, обробка fallback за рівнями безпеки, тестування на реальних пристроях.







