Налаштування Keystore для безпечного збереження даних в Android
Android Keystore System — не просто місце зберігання ключів. Це апаратно-ізольований контейнер (Trusted Execution Environment або Secure Element), з якого приватний ключ ніколи не покидає пристрій в plaintext. Крипто-операції виконуються всередині TEE. Більшість Android-додатків цим не користуються, зберігаючи токени у plaintext 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 series). Увімкнено через флаг:
.setIsStrongBoxBacked(true)
StrongBox повільніший (~100мс на операцію проти ~5мс для TEE), але ключи пережили б навіть компрометацію основного процесора. Для більшості додатків TEE достатньо. StrongBox має сенс для ключів підпису критичних транзакцій (фінтех, медицина).
Біометрична захист ключів
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(
0, // 0 = щоразу, >0 = timeout у секундах
KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL
)
AUTH_BIOMETRIC_STRONG на Android 11+ означає тільки Class 3 біометрію (Face ID-рівня або fingerprint sensor, що пройшов CDD-сертифікацію). Class 2 (більшість фронтальних камер без виділеного secure enclave) не підходить. При setUserAuthenticationRequired(true) спроба розшифрувати дані без попередньої біометрії викидає UserNotAuthenticatedException.
Біометричну аутентифікацію виконуйте через BiometricPrompt.CryptoObject(cipher) — це пов'язує біометричну сесію з конкретним Cipher-об'єктом, ініціалізованим вашим ключем. Важливо: без CryptoObject біометрія не дає дозволу на використання ключа.
Інвалідація ключа при зміні біометрії
.setInvalidatedByBiometricEnrollment(true)
За замовчуванням true — ключ інвалідується при додаванні нового відбитка або скидання біометрії. Це правильна поведінка для ключів шифрування даних. Обробіть KeyPermanentlyInvalidatedException при спробі використання інвалідованого ключа — генеруйте новий ключ та просіть користувача залогінитися заново.
Процес
Аудит — знаходимо все, що зберігається в plaintext SharedPreferences (токени, cookies, session IDs). Проектуємо схему: які дані шифруємо ключем з Keystore, які потребують біометрії. Реалізуємо CryptoManager з шифруванням/розшифруванням, інтегруємо з існуючим шаром зберігання (DataStore, Room). Тестуємо на емуляторах API 23–34 та реальних пристроях — поведінка Keystore на деяких користувацьких прошивках (Huawei без GMS) відрізняється.
Часові рамки — 1–3 дні в залежності від обсягу даних та вимог до біометричного захисту.







