Биометрия в приложении: Face ID, отпечаток, безопасное хранение

Биометрия — самый удобный способ авторизации в мобильном приложении. И самый часто реализованный неправильно. Разбираем, что хранится в Keychain/Keystore, что нельзя писать в SharedPreferences и как не сломать вход после смены пальца.

Биометрия в приложении выглядит просто: поставил Face ID при логине — готово. На практике 70% реализаций имеют дыры: токен в SharedPreferences, секрет в plain UserDefaults, обход через root, потеря доступа после смены биометрии на устройстве.

Главное правило

Биометрия — это не способ хранения секрета. Это способ разблокировать доступ к секрету, который хранится в системном защищённом хранилище. Сам отпечаток никогда не покидает Secure Enclave.

iOS — Keychain + LocalAuthentication

let access = SecAccessControlCreateWithFlags(
    nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
    .biometryCurrentSet, nil
)!

let attrs: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "refresh_token",
    kSecValueData as String: token.data(using: .utf8)!,
    kSecAttrAccessControl as String: access
]
SecItemAdd(attrs as CFDictionary, nil)

Флаг .biometryCurrentSet — ключ. При смене Face ID или добавлении нового отпечатка запись автоматически инвалидируется. Это защита от «жена записала свой отпечаток».

Android — EncryptedSharedPreferences + BiometricPrompt

Используйте BiometricPrompt из AndroidX, не legacy FingerprintManager. Ключ создаётся в Keystore с setUserAuthenticationRequired(true).

val spec = KeyGenParameterSpec.Builder("auth_key",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setUserAuthenticationRequired(true)
    .setInvalidatedByBiometricEnrollment(true)
    .build()

setInvalidatedByBiometricEnrollment(true) — аналог iOS-флага. При добавлении нового отпечатка ключ убивается.

Что нельзя

  • Токен в SharedPreferences без шифрования.
  • Пароль пользователя зашифрован в keystore — биометрия успешна → расшифровали и логиним. Это плохой паттерн — пароль никогда не должен лежать на устройстве.
  • Просто флаг is_biometry_ok в локальной БД. Обход через root за минуту.
  • Свой UI для биометрии. Используйте системный.

Правильный поток

  1. Первый вход — обычный пароль/OTP.
  2. Сервер выдаёт refresh-токен с длительным сроком (30-90 дней).
  3. Refresh-токен сохраняем в Keychain/Keystore с биометрической защитой.
  4. Следующие входы — биометрия → достали refresh → обменяли на access-токен.
  5. При смене биометрии на устройстве — токен умирает → пользователь логинится снова паролем.

Backup-сценарий

Биометрия — не единственный способ войти. Всегда добавляйте PIN-код приложения или возможность ввести пароль заново. Иначе после порезанного пальца или новой Face ID юзер останется без приложения навсегда.

Вывод

Биометрия — это разблокировка системного хранилища, не хранение секрета сама по себе. На iOS — Keychain + .biometryCurrentSet. На Android — Keystore + setInvalidatedByBiometricEnrollment. Бэкап — PIN или пароль. Без этих штрихов реализация ломается при первой же смене отпечатка.

Узнайте подробнее о наших компетенциях
Разработка, ИИ, автоматизация — что мы делаем и как.