Настройка Keystore для безопасного хранения данных в Android
Android Keystore System — не просто место хранения ключей. Это аппаратно-изолированный контейнер (Trusted Execution Environment или Secure Element), из которого приватный ключ никогда не покидает устройство в виде plaintext. Криптооперации выполняются внутри TEE. Большинство Android-приложений этим не пользуются, используя SharedPreferences для токенов.
Как правильно генерировать и использовать ключи
Генерация AES-ключа в Keystore:
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
keyGenerator.init(
KeyGenParameterSpec.Builder(
"my_secure_key_alias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(false) // true для биометрии
.setKeySize(256)
.build()
)
keyGenerator.generateKey()
После этого ключ живёт в Keystore. Шифруем данные через Cipher с этим ключом, сохраняем зашифрованный blob + IV в SharedPreferences или Room. Ключ из Keystore недоступен напрямую — нельзя экспортировать байты, только использовать для операций через JCE API.
Почему GCM, а не CBC. AES-GCM даёт аутентифицированное шифрование: при расшифровке проверяется MAC, и если данные модифицированы, Cipher.doFinal() бросает AEADBadTagException. AES-CBC этого не делает — битые данные расшифруются в мусор без ошибки.
StrongBox vs TEE
На устройствах с Android 9+ и аппаратным Secure Element доступен StrongBox Keymaster. Отличие от обычного TEE: ключи хранятся в физически отдельном чипе (Pixel 3+, Samsung Galaxy S серия). Включается флагом:
.setIsStrongBoxBacked(true)
StrongBox медленнее (~100мс на операцию против ~5мс у TEE), но ключи выживают даже при компрометации основного процессора. Для большинства приложений TEE достаточен. StrongBox имеет смысл для ключей подписи критичных транзакций (финтех, медицина).
Биометрическая защита ключей
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(
0, // 0 = каждый раз, >0 = таймаут в секундах
KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
)
AUTH_BIOMETRIC_STRONG на Android 11+ означает только Class 3 биометрию (Face ID-уровень или fingerprint sensor, прошедший CDD-сертификацию). Class 2 (большинство фронтальных камер без dedicated secure enclave) не подходит. При setUserAuthenticationRequired(true) попытка расшифровать данные без предварительной биометрии выбросит UserNotAuthenticatedException.
Биометрическую аутентификацию выполняем через BiometricPrompt.CryptoObject(cipher) — это связывает биометрическую сессию с конкретным Cipher-объектом, инициализированным вашим ключом. Это важно: без CryptoObject биометрия не дает разрешения на использование ключа.
Key invalidation при смене биометрии
.setInvalidatedByBiometricEnrollment(true)
По умолчанию true — ключ инвалидируется при добавлении нового отпечатка или сбросе биометрии. Это правильное поведение для ключей шифрования данных. Нужно обрабатывать KeyPermanentlyInvalidatedException при попытке использования инвалидированного ключа — в этом случае генерируем новый ключ и просим пользователя залогиниться заново.
Процесс работы
Аудит — находим всё, что хранится в SharedPreferences в plaintext (токены, cookies, session IDs). Проектируем схему: какие данные шифруем ключом из Keystore, какие требуют биометрии. Реализуем CryptoManager с шифрованием/расшифрованием, интегрируем с существующим слоем хранения данных (DataStore, Room). Тестируем на эмуляторах API 23–34 и на реальных устройствах — поведение Keystore на некоторых кастомных прошивках (Huawei без GMS) отличается.
Сроки — 1–3 дня в зависимости от объёма данных и требований к биометрической защите.







