Показать диалоговое окно биометрической аутентификации

Одним из методов защиты конфиденциальной информации или премиум-контента в вашем приложении является запрос биометрической аутентификации, например, с использованием распознавания лиц или отпечатков пальцев. В этом руководстве объясняется, как поддерживать потоки биометрического входа в вашем приложении.

Как правило, для первоначального входа на устройство следует использовать Credential Manager . Последующие повторные авторизации можно выполнять с помощью Biometric Prompt или Credential Manager. Преимущество использования Biometric Prompt заключается в том, что он предлагает больше возможностей настройки, тогда как Credential Manager предлагает единую реализацию для обоих потоков.

Укажите типы аутентификации, которые поддерживает ваше приложение.

Чтобы определить типы аутентификации, которые поддерживает ваше приложение, используйте интерфейс BiometricManager.Authenticators . Система позволяет вам объявлять следующие типы аутентификации:

BIOMETRIC_STRONG
Аутентификация с использованием биометрических данных класса 3 , как определено на странице определения совместимости Android .
BIOMETRIC_WEAK
Аутентификация с использованием биометрических данных класса 2 , как определено на странице определения совместимости Android .
DEVICE_CREDENTIAL
Аутентификация с использованием учетных данных блокировки экрана — PIN-кода, графического ключа или пароля пользователя.

Чтобы начать использовать аутентификатор, пользователю необходимо создать PIN-код, шаблон или пароль. Если у пользователя его еще нет, процесс биометрической регистрации предложит ему создать его.

Чтобы определить типы биометрической аутентификации, которые принимает ваше приложение, передайте тип аутентификации или побитовую комбинацию типов в метод setAllowedAuthenticators() . Следующий фрагмент кода показывает, как поддерживать аутентификацию с использованием биометрических данных класса 3 или учетных данных блокировки экрана.

Котлин

// Lets the user authenticate using either a Class 3 biometric or
// their lock screen credential (PIN, pattern, or password).
promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric login for my app")
        .setSubtitle("Log in using your biometric credential")
        .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
        .build()

Ява

// Lets user authenticate using either a Class 3 biometric or
// their lock screen credential (PIN, pattern, or password).
promptInfo = new BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric login for my app")
        .setSubtitle("Log in using your biometric credential")
        .setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
        .build();

Следующие комбинации типов аутентификаторов не поддерживаются в Android 10 (уровень API 29) и ниже: DEVICE_CREDENTIAL и BIOMETRIC_STRONG | DEVICE_CREDENTIAL . Для проверки наличия PIN-кода, шаблона или пароля в Android 10 и ниже используйте метод KeyguardManager.isDeviceSecure() .

Проверьте, доступна ли биометрическая аутентификация

После того, как вы решите, какие элементы аутентификации поддерживает ваше приложение, проверьте, доступны ли эти элементы. Для этого передайте ту же побитовую комбинацию типов, которую вы объявили с помощью метода setAllowedAuthenticators() в метод canAuthenticate() . При необходимости вызовите действие намерения ACTION_BIOMETRIC_ENROLL . В дополнительном намерении укажите набор аутентификаторов, которые принимает ваше приложение. Это намерение предлагает пользователю зарегистрировать учетные данные для аутентификатора, который принимает ваше приложение.

Котлин

val biometricManager = BiometricManager.from(this)
when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {
    BiometricManager.BIOMETRIC_SUCCESS ->
        Log.d("MY_APP_TAG", "App can authenticate using biometrics.")
    BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
        Log.e("MY_APP_TAG", "No biometric features available on this device.")
    BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
        Log.e("MY_APP_TAG", "Biometric features are currently unavailable.")
    BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
        // Prompts the user to create credentials that your app accepts.
        val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
            putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
                BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
        }
        startActivityForResult(enrollIntent, REQUEST_CODE)
    }
}

Ява

BiometricManager biometricManager = BiometricManager.from(this);
switch (biometricManager.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)) {
    case BiometricManager.BIOMETRIC_SUCCESS:
        Log.d("MY_APP_TAG", "App can authenticate using biometrics.");
        break;
    case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
        Log.e("MY_APP_TAG", "No biometric features available on this device.");
        break;
    case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
        Log.e("MY_APP_TAG", "Biometric features are currently unavailable.");
        break;
    case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
        // Prompts the user to create credentials that your app accepts.
        final Intent enrollIntent = new Intent(Settings.ACTION_BIOMETRIC_ENROLL);
        enrollIntent.putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
                BIOMETRIC_STRONG | DEVICE_CREDENTIAL);
        startActivityForResult(enrollIntent, REQUEST_CODE);
        break;
}

Определите, как пользователь прошел аутентификацию

После аутентификации пользователя вы можете проверить, использовал ли он учетные данные устройства или биометрические данные, вызвав getAuthenticationType() .

Отобразить приглашение на вход

Чтобы отобразить системное приглашение, которое запрашивает у пользователя аутентификацию с использованием биометрических учетных данных, используйте Биометрическую библиотеку . Этот системный диалог является единым для всех приложений, которые его используют, создавая более надежный пользовательский опыт. Пример диалога показан на рисунке 1.

Скриншот диалогового окна
Рисунок 1. Системный диалог, запрашивающий биометрическую аутентификацию.

Чтобы добавить биометрическую аутентификацию в свое приложение с помощью биометрической библиотеки, выполните следующие действия:

  1. В файле build.gradle вашего модуля приложения добавьте зависимость от библиотеки androidx.biometric .

  2. В действии или фрагменте, где размещен диалог биометрического входа, отобразите диалог, используя логику, показанную в следующем фрагменте кода:

    Котлин

    private lateinit var executor: Executor
    private lateinit var biometricPrompt: BiometricPrompt
    private lateinit var promptInfo: BiometricPrompt.PromptInfo
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        executor = ContextCompat.getMainExecutor(this)
        biometricPrompt = BiometricPrompt(this, executor,
                object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationError(errorCode: Int,
                    errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                Toast.makeText(applicationContext,
                    "Authentication error: $errString", Toast.LENGTH_SHORT)
                    .show()
            }
    
            override fun onAuthenticationSucceeded(
                    result: BiometricPrompt.AuthenticationResult) {
                super.onAuthenticationSucceeded(result)
                Toast.makeText(applicationContext,
                    "Authentication succeeded!", Toast.LENGTH_SHORT)
                    .show()
            }
    
            override fun onAuthenticationFailed() {
                super.onAuthenticationFailed()
                Toast.makeText(applicationContext, "Authentication failed",
                    Toast.LENGTH_SHORT)
                    .show()
            }
        })
    
        promptInfo = BiometricPrompt.PromptInfo.Builder()
                .setTitle("Biometric login for my app")
                .setSubtitle("Log in using your biometric credential")
                .setNegativeButtonText("Use account password")
                .build()
    
        // Prompt appears when user clicks "Log in".
        // Consider integrating with the keystore to unlock cryptographic operations,
        // if needed by your app.
        val biometricLoginButton =
                findViewById<Button>(R.id.biometric_login)
        biometricLoginButton.setOnClickListener {
            biometricPrompt.authenticate(promptInfo)
        }
    }

    Ява

    private Executor executor;
    private BiometricPrompt biometricPrompt;
    private BiometricPrompt.PromptInfo promptInfo;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        executor = ContextCompat.getMainExecutor(this);
        biometricPrompt = new BiometricPrompt(MainActivity.this,
                executor, new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode,
                    @NonNull CharSequence errString) {
                super.onAuthenticationError(errorCode, errString);
                Toast.makeText(getApplicationContext(),
                    "Authentication error: " + errString, Toast.LENGTH_SHORT)
                    .show();
            }
    
            @Override
            public void onAuthenticationSucceeded(
                    @NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                Toast.makeText(getApplicationContext(),
                    "Authentication succeeded!", Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onAuthenticationFailed() {
                super.onAuthenticationFailed();
                Toast.makeText(getApplicationContext(), "Authentication failed",
                    Toast.LENGTH_SHORT)
                    .show();
            }
        });
    
        promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("Biometric login for my app")
                .setSubtitle("Log in using your biometric credential")
                .setNegativeButtonText("Use account password")
                .build();
    
        // Prompt appears when user clicks "Log in".
        // Consider integrating with the keystore to unlock cryptographic operations,
        // if needed by your app.
        Button biometricLoginButton = findViewById(R.id.biometric_login);
        biometricLoginButton.setOnClickListener(view -> {
                biometricPrompt.authenticate(promptInfo);
        });
    }

Используйте криптографическое решение, которое зависит от аутентификации

Для дальнейшей защиты конфиденциальной информации в вашем приложении вы можете включить криптографию в рабочий процесс биометрической аутентификации с помощью экземпляра CryptoObject . Фреймворк поддерживает следующие криптографические объекты: Signature , Cipher и Mac .

После того, как пользователь успешно аутентифицируется с помощью биометрического запроса, ваше приложение может выполнить криптографическую операцию. Например, если вы аутентифицируетесь с помощью объекта Cipher , ваше приложение может затем выполнить шифрование и дешифрование с помощью объекта SecretKey .

В следующих разделах рассматриваются примеры использования объекта Cipher и объекта SecretKey для шифрования данных. В каждом примере используются следующие методы:

Котлин

private fun generateSecretKey(keyGenParameterSpec: KeyGenParameterSpec) {
    val keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
    keyGenerator.init(keyGenParameterSpec)
    keyGenerator.generateKey()
}

private fun getSecretKey(): SecretKey {
    val keyStore = KeyStore.getInstance("AndroidKeyStore")

    // Before the keystore can be accessed, it must be loaded.
    keyStore.load(null)
    return keyStore.getKey(KEY_NAME, null) as SecretKey
}

private fun getCipher(): Cipher {
    return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
            + KeyProperties.BLOCK_MODE_CBC + "/"
            + KeyProperties.ENCRYPTION_PADDING_PKCS7)
}

Ява

private void generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
    keyGenerator.init(keyGenParameterSpec);
    keyGenerator.generateKey();
}

private SecretKey getSecretKey() {
    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");

    // Before the keystore can be accessed, it must be loaded.
    keyStore.load(null);
    return ((SecretKey)keyStore.getKey(KEY_NAME, null));
}

private Cipher getCipher() {
    return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
            + KeyProperties.BLOCK_MODE_CBC + "/"
            + KeyProperties.ENCRYPTION_PADDING_PKCS7);
}

Аутентификация только с использованием биометрических данных

Если ваше приложение использует секретный ключ, для разблокировки которого требуются биометрические данные, пользователь должен будет подтверждать свои биометрические данные каждый раз, прежде чем ваше приложение получит доступ к ключу.

Чтобы зашифровать конфиденциальную информацию только после аутентификации пользователя с использованием биометрических данных, выполните следующие действия:

  1. Создайте ключ, использующий следующую конфигурацию KeyGenParameterSpec :

    Котлин

    generateSecretKey(KeyGenParameterSpec.Builder(
            KEY_NAME,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .setUserAuthenticationRequired(true)
            // Invalidate the keys if the user has registered a new biometric
            // credential, such as a new fingerprint. Can call this method only
            // on Android 7.0 (API level 24) or higher. The variable
            // "invalidatedByBiometricEnrollment" is true by default.
            .setInvalidatedByBiometricEnrollment(true)
            .build())

    Ява

    generateSecretKey(new KeyGenParameterSpec.Builder(
            KEY_NAME,
            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .setUserAuthenticationRequired(true)
            // Invalidate the keys if the user has registered a new biometric
            // credential, such as a new fingerprint. Can call this method only
            // on Android 7.0 (API level 24) or higher. The variable
            // "invalidatedByBiometricEnrollment" is true by default.
            .setInvalidatedByBiometricEnrollment(true)
            .build());
  2. Запустите рабочий процесс биометрической аутентификации, включающий шифр:

    Котлин

    biometricLoginButton.setOnClickListener {
        // Exceptions are unhandled within this snippet.
        val cipher = getCipher()
        val secretKey = getSecretKey()
        cipher.init(Cipher.ENCRYPT_MODE, secretKey)
        biometricPrompt.authenticate(promptInfo,
                BiometricPrompt.CryptoObject(cipher))
    }

    Ява

    biometricLoginButton.setOnClickListener(view -> {
        // Exceptions are unhandled within this snippet.
        Cipher cipher = getCipher();
        SecretKey secretKey = getSecretKey();
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        biometricPrompt.authenticate(promptInfo,
                new BiometricPrompt.CryptoObject(cipher));
    });
  3. В обратных вызовах биометрической аутентификации используйте секретный ключ для шифрования конфиденциальной информации:

    Котлин

    override fun onAuthenticationSucceeded(
            result: BiometricPrompt.AuthenticationResult) {
        val encryptedInfo: ByteArray = result.cryptoObject.cipher?.doFinal(
            // plaintext-string text is whatever data the developer would like
            // to encrypt. It happens to be plain-text in this example, but it
            // can be anything
                plaintext-string.toByteArray(Charset.defaultCharset())
        )
        Log.d("MY_APP_TAG", "Encrypted information: " +
                Arrays.toString(encryptedInfo))
    }

    Ява

    @Override
    public void onAuthenticationSucceeded(
            @NonNull BiometricPrompt.AuthenticationResult result) {
        // NullPointerException is unhandled; use Objects.requireNonNull().
        byte[] encryptedInfo = result.getCryptoObject().getCipher().doFinal(
            // plaintext-string text is whatever data the developer would like
            // to encrypt. It happens to be plain-text in this example, but it
            // can be anything
                plaintext-string.getBytes(Charset.defaultCharset()));
        Log.d("MY_APP_TAG", "Encrypted information: " +
                Arrays.toString(encryptedInfo));
    }

Аутентификация с использованием биометрических данных или данных экрана блокировки

Вы можете использовать секретный ключ, который позволяет проводить аутентификацию с использованием биометрических учетных данных или учетных данных экрана блокировки (PIN-код, шаблон или пароль). При настройке этого ключа укажите период действия. В течение этого периода времени ваше приложение может выполнять несколько криптографических операций без необходимости повторной аутентификации пользователя.

Чтобы зашифровать конфиденциальную информацию после аутентификации пользователя с использованием биометрических данных или данных экрана блокировки, выполните следующие действия:

  1. Создайте ключ, использующий следующую конфигурацию KeyGenParameterSpec :

    Котлин

    generateSecretKey(KeyGenParameterSpec.Builder(
        KEY_NAME,
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        .setUserAuthenticationRequired(true)
        .setUserAuthenticationParameters(VALIDITY_DURATION_SECONDS,
                ALLOWED_AUTHENTICATORS)
        .build())

    Ява

    generateSecretKey(new KeyGenParameterSpec.Builder(
        KEY_NAME,
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        .setUserAuthenticationRequired(true)
        .setUserAuthenticationParameters(VALIDITY_DURATION_SECONDS,
                ALLOWED_AUTHENTICATORS)
        .build());
  2. В течение периода времени VALIDITY_DURATION_SECONDS после аутентификации пользователя зашифруйте конфиденциальную информацию:

    Котлин

    private fun encryptSecretInformation() {
        // Exceptions are unhandled for getCipher() and getSecretKey().
        val cipher = getCipher()
        val secretKey = getSecretKey()
        try {
            cipher.init(Cipher.ENCRYPT_MODE, secretKey)
            val encryptedInfo: ByteArray = cipher.doFinal(
                // plaintext-string text is whatever data the developer would
                // like to encrypt. It happens to be plain-text in this example,
                // but it can be anything
                    plaintext-string.toByteArray(Charset.defaultCharset()))
            Log.d("MY_APP_TAG", "Encrypted information: " +
                    Arrays.toString(encryptedInfo))
        } catch (e: InvalidKeyException) {
            Log.e("MY_APP_TAG", "Key is invalid.")
        } catch (e: UserNotAuthenticatedException) {
            Log.d("MY_APP_TAG", "The key's validity timed out.")
            biometricPrompt.authenticate(promptInfo)
        }

    Ява

    private void encryptSecretInformation() {
        // Exceptions are unhandled for getCipher() and getSecretKey().
        Cipher cipher = getCipher();
        SecretKey secretKey = getSecretKey();
        try {
            // NullPointerException is unhandled; use Objects.requireNonNull().
            ciper.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] encryptedInfo = cipher.doFinal(
                // plaintext-string text is whatever data the developer would
                // like to encrypt. It happens to be plain-text in this example,
                // but it can be anything
                    plaintext-string.getBytes(Charset.defaultCharset()));
        } catch (InvalidKeyException e) {
            Log.e("MY_APP_TAG", "Key is invalid.");
        } catch (UserNotAuthenticatedException e) {
            Log.d("MY_APP_TAG", "The key's validity timed out.");
            biometricPrompt.authenticate(promptInfo);
        }
    }

Аутентификация с использованием ключей аутентификации на каждое использование

Вы можете обеспечить поддержку ключей аутентификации на использование в вашем экземпляре BiometricPrompt . Такой ключ требует от пользователя предоставления либо биометрических учетных данных, либо учетных данных устройства каждый раз, когда вашему приложению требуется доступ к данным, защищенным этим ключом. Ключи аутентификации на использование могут быть полезны для транзакций с высокой стоимостью, таких как совершение крупного платежа или обновление медицинских записей человека.

Чтобы связать объект BiometricPrompt с ключом аутентификации на каждое использование, добавьте код, аналогичный следующему:

Котлин

val authPerOpKeyGenParameterSpec =
        KeyGenParameterSpec.Builder("myKeystoreAlias", key-purpose)
    // Accept either a biometric credential or a device credential.
    // To accept only one type of credential, include only that type as the
    // second argument.
    .setUserAuthenticationParameters(0 /* duration */,
            KeyProperties.AUTH_BIOMETRIC_STRONG or
            KeyProperties.AUTH_DEVICE_CREDENTIAL)
    .build()

Ява

KeyGenParameterSpec authPerOpKeyGenParameterSpec =
        new KeyGenParameterSpec.Builder("myKeystoreAlias", key-purpose)
    // Accept either a biometric credential or a device credential.
    // To accept only one type of credential, include only that type as the
    // second argument.
    .setUserAuthenticationParameters(0 /* duration */,
            KeyProperties.AUTH_BIOMETRIC_STRONG |
            KeyProperties.AUTH_DEVICE_CREDENTIAL)
    .build();

Аутентификация без явного действия пользователя

По умолчанию система требует от пользователей выполнения определенного действия, например нажатия кнопки, после того, как их биометрические данные будут приняты. Такая конфигурация предпочтительна, если ваше приложение показывает диалоговое окно для подтверждения чувствительного или высокорискового действия, например совершения покупки.

Однако если ваше приложение показывает диалоговое окно биометрической аутентификации для действия с меньшим риском, вы можете дать системе подсказку о том, что пользователю не нужно подтверждать аутентификацию. Эта подсказка может позволить пользователю быстрее просматривать контент в вашем приложении после повторной аутентификации с использованием пассивной модальности, например распознавания по лицу или радужной оболочке глаза. Чтобы предоставить эту подсказку, передайте false в метод setConfirmationRequired() .

На рисунке 2 показаны две версии одного и того же диалога. Одна версия требует явного действия пользователя, а другая — нет.

Скриншот диалогаСкриншот диалога
Рисунок 2. Аутентификация по лицу без подтверждения пользователя (вверху) и с подтверждением пользователя (внизу).

В следующем фрагменте кода показано, как представить диалоговое окно, не требующее явного действия пользователя для завершения процесса аутентификации:

Котлин

// Lets the user authenticate without performing an action, such as pressing a
// button, after their biometric credential is accepted.
promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric login for my app")
        .setSubtitle("Log in using your biometric credential")
        .setNegativeButtonText("Use account password")
        .setConfirmationRequired(false)
        .build()

Ява

// Lets the user authenticate without performing an action, such as pressing a
// button, after their biometric credential is accepted.
promptInfo = new BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric login for my app")
        .setSubtitle("Log in using your biometric credential")
        .setNegativeButtonText("Use account password")
        .setConfirmationRequired(false)
        .build();

Разрешить откат к небиометрическим учетным данным

Если вы хотите, чтобы ваше приложение разрешало аутентификацию с использованием биометрических данных или учетных данных устройства, вы можете объявить, что ваше приложение поддерживает учетные данные устройства , включив DEVICE_CREDENTIAL в набор значений, которые вы передаете в setAllowedAuthenticators() .

Если в настоящее время ваше приложение использует createConfirmDeviceCredentialIntent() или setDeviceCredentialAllowed() для предоставления этой возможности, переключитесь на использование setAllowedAuthenticators() .

Дополнительные ресурсы

Чтобы узнать больше о биометрической аутентификации на Android, ознакомьтесь со следующими ресурсами.

Записи в блоге

,

Одним из методов защиты конфиденциальной информации или премиум-контента в вашем приложении является запрос биометрической аутентификации, например, с использованием распознавания лиц или отпечатков пальцев. В этом руководстве объясняется, как поддерживать потоки биометрического входа в вашем приложении.

Как правило, для первоначального входа на устройство следует использовать Credential Manager . Последующие повторные авторизации можно выполнять с помощью Biometric Prompt или Credential Manager. Преимущество использования Biometric Prompt заключается в том, что он предлагает больше возможностей настройки, тогда как Credential Manager предлагает единую реализацию для обоих потоков.

Укажите типы аутентификации, которые поддерживает ваше приложение.

Чтобы определить типы аутентификации, которые поддерживает ваше приложение, используйте интерфейс BiometricManager.Authenticators . Система позволяет вам объявлять следующие типы аутентификации:

BIOMETRIC_STRONG
Аутентификация с использованием биометрических данных класса 3 , как определено на странице определения совместимости Android .
BIOMETRIC_WEAK
Аутентификация с использованием биометрических данных класса 2 , как определено на странице определения совместимости Android .
DEVICE_CREDENTIAL
Аутентификация с использованием учетных данных блокировки экрана — PIN-кода, графического ключа или пароля пользователя.

Чтобы начать использовать аутентификатор, пользователю необходимо создать PIN-код, шаблон или пароль. Если у пользователя его еще нет, процесс биометрической регистрации предложит ему создать его.

Чтобы определить типы биометрической аутентификации, которые принимает ваше приложение, передайте тип аутентификации или побитовую комбинацию типов в метод setAllowedAuthenticators() . Следующий фрагмент кода показывает, как поддерживать аутентификацию с использованием биометрических данных класса 3 или учетных данных блокировки экрана.

Котлин

// Lets the user authenticate using either a Class 3 biometric or
// their lock screen credential (PIN, pattern, or password).
promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric login for my app")
        .setSubtitle("Log in using your biometric credential")
        .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
        .build()

Ява

// Lets user authenticate using either a Class 3 biometric or
// their lock screen credential (PIN, pattern, or password).
promptInfo = new BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric login for my app")
        .setSubtitle("Log in using your biometric credential")
        .setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
        .build();

Следующие комбинации типов аутентификаторов не поддерживаются в Android 10 (уровень API 29) и ниже: DEVICE_CREDENTIAL и BIOMETRIC_STRONG | DEVICE_CREDENTIAL . Для проверки наличия PIN-кода, шаблона или пароля в Android 10 и ниже используйте метод KeyguardManager.isDeviceSecure() .

Проверьте, доступна ли биометрическая аутентификация

После того, как вы решите, какие элементы аутентификации поддерживает ваше приложение, проверьте, доступны ли эти элементы. Для этого передайте ту же побитовую комбинацию типов, которую вы объявили с помощью метода setAllowedAuthenticators() в метод canAuthenticate() . При необходимости вызовите действие намерения ACTION_BIOMETRIC_ENROLL . В дополнительном намерении укажите набор аутентификаторов, которые принимает ваше приложение. Это намерение предлагает пользователю зарегистрировать учетные данные для аутентификатора, который принимает ваше приложение.

Котлин

val biometricManager = BiometricManager.from(this)
when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {
    BiometricManager.BIOMETRIC_SUCCESS ->
        Log.d("MY_APP_TAG", "App can authenticate using biometrics.")
    BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
        Log.e("MY_APP_TAG", "No biometric features available on this device.")
    BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
        Log.e("MY_APP_TAG", "Biometric features are currently unavailable.")
    BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
        // Prompts the user to create credentials that your app accepts.
        val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
            putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
                BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
        }
        startActivityForResult(enrollIntent, REQUEST_CODE)
    }
}

Ява

BiometricManager biometricManager = BiometricManager.from(this);
switch (biometricManager.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)) {
    case BiometricManager.BIOMETRIC_SUCCESS:
        Log.d("MY_APP_TAG", "App can authenticate using biometrics.");
        break;
    case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
        Log.e("MY_APP_TAG", "No biometric features available on this device.");
        break;
    case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
        Log.e("MY_APP_TAG", "Biometric features are currently unavailable.");
        break;
    case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
        // Prompts the user to create credentials that your app accepts.
        final Intent enrollIntent = new Intent(Settings.ACTION_BIOMETRIC_ENROLL);
        enrollIntent.putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
                BIOMETRIC_STRONG | DEVICE_CREDENTIAL);
        startActivityForResult(enrollIntent, REQUEST_CODE);
        break;
}

Определите, как пользователь прошел аутентификацию

После аутентификации пользователя вы можете проверить, использовал ли он учетные данные устройства или биометрические данные, вызвав getAuthenticationType() .

Отобразить приглашение на вход

Чтобы отобразить системное приглашение, которое запрашивает у пользователя аутентификацию с использованием биометрических учетных данных, используйте Биометрическую библиотеку . Этот системный диалог является единым для всех приложений, которые его используют, создавая более надежный пользовательский опыт. Пример диалога показан на рисунке 1.

Скриншот диалогового окна
Рисунок 1. Системный диалог, запрашивающий биометрическую аутентификацию.

Чтобы добавить биометрическую аутентификацию в свое приложение с помощью биометрической библиотеки, выполните следующие действия:

  1. В файле build.gradle вашего модуля приложения добавьте зависимость от библиотеки androidx.biometric .

  2. В действии или фрагменте, где размещен диалог биометрического входа, отобразите диалог, используя логику, показанную в следующем фрагменте кода:

    Котлин

    private lateinit var executor: Executor
    private lateinit var biometricPrompt: BiometricPrompt
    private lateinit var promptInfo: BiometricPrompt.PromptInfo
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        executor = ContextCompat.getMainExecutor(this)
        biometricPrompt = BiometricPrompt(this, executor,
                object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationError(errorCode: Int,
                    errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                Toast.makeText(applicationContext,
                    "Authentication error: $errString", Toast.LENGTH_SHORT)
                    .show()
            }
    
            override fun onAuthenticationSucceeded(
                    result: BiometricPrompt.AuthenticationResult) {
                super.onAuthenticationSucceeded(result)
                Toast.makeText(applicationContext,
                    "Authentication succeeded!", Toast.LENGTH_SHORT)
                    .show()
            }
    
            override fun onAuthenticationFailed() {
                super.onAuthenticationFailed()
                Toast.makeText(applicationContext, "Authentication failed",
                    Toast.LENGTH_SHORT)
                    .show()
            }
        })
    
        promptInfo = BiometricPrompt.PromptInfo.Builder()
                .setTitle("Biometric login for my app")
                .setSubtitle("Log in using your biometric credential")
                .setNegativeButtonText("Use account password")
                .build()
    
        // Prompt appears when user clicks "Log in".
        // Consider integrating with the keystore to unlock cryptographic operations,
        // if needed by your app.
        val biometricLoginButton =
                findViewById<Button>(R.id.biometric_login)
        biometricLoginButton.setOnClickListener {
            biometricPrompt.authenticate(promptInfo)
        }
    }

    Ява

    private Executor executor;
    private BiometricPrompt biometricPrompt;
    private BiometricPrompt.PromptInfo promptInfo;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        executor = ContextCompat.getMainExecutor(this);
        biometricPrompt = new BiometricPrompt(MainActivity.this,
                executor, new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode,
                    @NonNull CharSequence errString) {
                super.onAuthenticationError(errorCode, errString);
                Toast.makeText(getApplicationContext(),
                    "Authentication error: " + errString, Toast.LENGTH_SHORT)
                    .show();
            }
    
            @Override
            public void onAuthenticationSucceeded(
                    @NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                Toast.makeText(getApplicationContext(),
                    "Authentication succeeded!", Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onAuthenticationFailed() {
                super.onAuthenticationFailed();
                Toast.makeText(getApplicationContext(), "Authentication failed",
                    Toast.LENGTH_SHORT)
                    .show();
            }
        });
    
        promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("Biometric login for my app")
                .setSubtitle("Log in using your biometric credential")
                .setNegativeButtonText("Use account password")
                .build();
    
        // Prompt appears when user clicks "Log in".
        // Consider integrating with the keystore to unlock cryptographic operations,
        // if needed by your app.
        Button biometricLoginButton = findViewById(R.id.biometric_login);
        biometricLoginButton.setOnClickListener(view -> {
                biometricPrompt.authenticate(promptInfo);
        });
    }

Используйте криптографическое решение, которое зависит от аутентификации

Для дальнейшей защиты конфиденциальной информации в вашем приложении вы можете включить криптографию в рабочий процесс биометрической аутентификации с помощью экземпляра CryptoObject . Фреймворк поддерживает следующие криптографические объекты: Signature , Cipher и Mac .

После того, как пользователь успешно аутентифицируется с помощью биометрического запроса, ваше приложение может выполнить криптографическую операцию. Например, если вы аутентифицируетесь с помощью объекта Cipher , ваше приложение может затем выполнить шифрование и дешифрование с помощью объекта SecretKey .

В следующих разделах рассматриваются примеры использования объекта Cipher и объекта SecretKey для шифрования данных. В каждом примере используются следующие методы:

Котлин

private fun generateSecretKey(keyGenParameterSpec: KeyGenParameterSpec) {
    val keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
    keyGenerator.init(keyGenParameterSpec)
    keyGenerator.generateKey()
}

private fun getSecretKey(): SecretKey {
    val keyStore = KeyStore.getInstance("AndroidKeyStore")

    // Before the keystore can be accessed, it must be loaded.
    keyStore.load(null)
    return keyStore.getKey(KEY_NAME, null) as SecretKey
}

private fun getCipher(): Cipher {
    return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
            + KeyProperties.BLOCK_MODE_CBC + "/"
            + KeyProperties.ENCRYPTION_PADDING_PKCS7)
}

Ява

private void generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
    keyGenerator.init(keyGenParameterSpec);
    keyGenerator.generateKey();
}

private SecretKey getSecretKey() {
    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");

    // Before the keystore can be accessed, it must be loaded.
    keyStore.load(null);
    return ((SecretKey)keyStore.getKey(KEY_NAME, null));
}

private Cipher getCipher() {
    return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
            + KeyProperties.BLOCK_MODE_CBC + "/"
            + KeyProperties.ENCRYPTION_PADDING_PKCS7);
}

Аутентификация только с использованием биометрических данных

Если ваше приложение использует секретный ключ, для разблокировки которого требуются биометрические данные, пользователь должен будет подтверждать свои биометрические данные каждый раз, прежде чем ваше приложение получит доступ к ключу.

Чтобы зашифровать конфиденциальную информацию только после аутентификации пользователя с использованием биометрических данных, выполните следующие действия:

  1. Создайте ключ, использующий следующую конфигурацию KeyGenParameterSpec :

    Котлин

    generateSecretKey(KeyGenParameterSpec.Builder(
            KEY_NAME,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .setUserAuthenticationRequired(true)
            // Invalidate the keys if the user has registered a new biometric
            // credential, such as a new fingerprint. Can call this method only
            // on Android 7.0 (API level 24) or higher. The variable
            // "invalidatedByBiometricEnrollment" is true by default.
            .setInvalidatedByBiometricEnrollment(true)
            .build())

    Ява

    generateSecretKey(new KeyGenParameterSpec.Builder(
            KEY_NAME,
            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .setUserAuthenticationRequired(true)
            // Invalidate the keys if the user has registered a new biometric
            // credential, such as a new fingerprint. Can call this method only
            // on Android 7.0 (API level 24) or higher. The variable
            // "invalidatedByBiometricEnrollment" is true by default.
            .setInvalidatedByBiometricEnrollment(true)
            .build());
  2. Запустите рабочий процесс биометрической аутентификации, включающий шифр:

    Котлин

    biometricLoginButton.setOnClickListener {
        // Exceptions are unhandled within this snippet.
        val cipher = getCipher()
        val secretKey = getSecretKey()
        cipher.init(Cipher.ENCRYPT_MODE, secretKey)
        biometricPrompt.authenticate(promptInfo,
                BiometricPrompt.CryptoObject(cipher))
    }

    Ява

    biometricLoginButton.setOnClickListener(view -> {
        // Exceptions are unhandled within this snippet.
        Cipher cipher = getCipher();
        SecretKey secretKey = getSecretKey();
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        biometricPrompt.authenticate(promptInfo,
                new BiometricPrompt.CryptoObject(cipher));
    });
  3. В обратных вызовах биометрической аутентификации используйте секретный ключ для шифрования конфиденциальной информации:

    Котлин

    override fun onAuthenticationSucceeded(
            result: BiometricPrompt.AuthenticationResult) {
        val encryptedInfo: ByteArray = result.cryptoObject.cipher?.doFinal(
            // plaintext-string text is whatever data the developer would like
            // to encrypt. It happens to be plain-text in this example, but it
            // can be anything
                plaintext-string.toByteArray(Charset.defaultCharset())
        )
        Log.d("MY_APP_TAG", "Encrypted information: " +
                Arrays.toString(encryptedInfo))
    }

    Ява

    @Override
    public void onAuthenticationSucceeded(
            @NonNull BiometricPrompt.AuthenticationResult result) {
        // NullPointerException is unhandled; use Objects.requireNonNull().
        byte[] encryptedInfo = result.getCryptoObject().getCipher().doFinal(
            // plaintext-string text is whatever data the developer would like
            // to encrypt. It happens to be plain-text in this example, but it
            // can be anything
                plaintext-string.getBytes(Charset.defaultCharset()));
        Log.d("MY_APP_TAG", "Encrypted information: " +
                Arrays.toString(encryptedInfo));
    }

Аутентификация с использованием биометрических данных или данных экрана блокировки

Вы можете использовать секретный ключ, который позволяет проводить аутентификацию с использованием биометрических учетных данных или учетных данных экрана блокировки (PIN-код, шаблон или пароль). При настройке этого ключа укажите период действия. В течение этого периода времени ваше приложение может выполнять несколько криптографических операций без необходимости повторной аутентификации пользователя.

Чтобы зашифровать конфиденциальную информацию после аутентификации пользователя с использованием биометрических данных или данных экрана блокировки, выполните следующие действия:

  1. Создайте ключ, использующий следующую конфигурацию KeyGenParameterSpec :

    Котлин

    generateSecretKey(KeyGenParameterSpec.Builder(
        KEY_NAME,
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        .setUserAuthenticationRequired(true)
        .setUserAuthenticationParameters(VALIDITY_DURATION_SECONDS,
                ALLOWED_AUTHENTICATORS)
        .build())

    Ява

    generateSecretKey(new KeyGenParameterSpec.Builder(
        KEY_NAME,
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        .setUserAuthenticationRequired(true)
        .setUserAuthenticationParameters(VALIDITY_DURATION_SECONDS,
                ALLOWED_AUTHENTICATORS)
        .build());
  2. В течение периода времени VALIDITY_DURATION_SECONDS после аутентификации пользователя зашифруйте конфиденциальную информацию:

    Котлин

    private fun encryptSecretInformation() {
        // Exceptions are unhandled for getCipher() and getSecretKey().
        val cipher = getCipher()
        val secretKey = getSecretKey()
        try {
            cipher.init(Cipher.ENCRYPT_MODE, secretKey)
            val encryptedInfo: ByteArray = cipher.doFinal(
                // plaintext-string text is whatever data the developer would
                // like to encrypt. It happens to be plain-text in this example,
                // but it can be anything
                    plaintext-string.toByteArray(Charset.defaultCharset()))
            Log.d("MY_APP_TAG", "Encrypted information: " +
                    Arrays.toString(encryptedInfo))
        } catch (e: InvalidKeyException) {
            Log.e("MY_APP_TAG", "Key is invalid.")
        } catch (e: UserNotAuthenticatedException) {
            Log.d("MY_APP_TAG", "The key's validity timed out.")
            biometricPrompt.authenticate(promptInfo)
        }

    Ява

    private void encryptSecretInformation() {
        // Exceptions are unhandled for getCipher() and getSecretKey().
        Cipher cipher = getCipher();
        SecretKey secretKey = getSecretKey();
        try {
            // NullPointerException is unhandled; use Objects.requireNonNull().
            ciper.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] encryptedInfo = cipher.doFinal(
                // plaintext-string text is whatever data the developer would
                // like to encrypt. It happens to be plain-text in this example,
                // but it can be anything
                    plaintext-string.getBytes(Charset.defaultCharset()));
        } catch (InvalidKeyException e) {
            Log.e("MY_APP_TAG", "Key is invalid.");
        } catch (UserNotAuthenticatedException e) {
            Log.d("MY_APP_TAG", "The key's validity timed out.");
            biometricPrompt.authenticate(promptInfo);
        }
    }

Аутентификация с использованием ключей аутентификации на каждое использование

Вы можете обеспечить поддержку ключей аутентификации на использование в вашем экземпляре BiometricPrompt . Такой ключ требует от пользователя предоставления либо биометрических учетных данных, либо учетных данных устройства каждый раз, когда вашему приложению требуется доступ к данным, защищенным этим ключом. Ключи аутентификации на использование могут быть полезны для транзакций с высокой стоимостью, таких как совершение крупного платежа или обновление медицинских записей человека.

Чтобы связать объект BiometricPrompt с ключом аутентификации на каждое использование, добавьте код, аналогичный следующему:

Котлин

val authPerOpKeyGenParameterSpec =
        KeyGenParameterSpec.Builder("myKeystoreAlias", key-purpose)
    // Accept either a biometric credential or a device credential.
    // To accept only one type of credential, include only that type as the
    // second argument.
    .setUserAuthenticationParameters(0 /* duration */,
            KeyProperties.AUTH_BIOMETRIC_STRONG or
            KeyProperties.AUTH_DEVICE_CREDENTIAL)
    .build()

Ява

KeyGenParameterSpec authPerOpKeyGenParameterSpec =
        new KeyGenParameterSpec.Builder("myKeystoreAlias", key-purpose)
    // Accept either a biometric credential or a device credential.
    // To accept only one type of credential, include only that type as the
    // second argument.
    .setUserAuthenticationParameters(0 /* duration */,
            KeyProperties.AUTH_BIOMETRIC_STRONG |
            KeyProperties.AUTH_DEVICE_CREDENTIAL)
    .build();

Аутентификация без явного действия пользователя

По умолчанию система требует от пользователей выполнения определенного действия, например нажатия кнопки, после того, как их биометрические данные будут приняты. Такая конфигурация предпочтительна, если ваше приложение показывает диалоговое окно для подтверждения чувствительного или высокорискового действия, например совершения покупки.

Однако если ваше приложение показывает диалоговое окно биометрической аутентификации для действия с меньшим риском, вы можете дать системе подсказку о том, что пользователю не нужно подтверждать аутентификацию. Эта подсказка может позволить пользователю быстрее просматривать контент в вашем приложении после повторной аутентификации с использованием пассивной модальности, например распознавания по лицу или радужной оболочке глаза. Чтобы предоставить эту подсказку, передайте false в метод setConfirmationRequired() .

На рисунке 2 показаны две версии одного и того же диалога. Одна версия требует явного действия пользователя, а другая — нет.

Скриншот диалогаСкриншот диалога
Рисунок 2. Аутентификация по лицу без подтверждения пользователя (вверху) и с подтверждением пользователя (внизу).

В следующем фрагменте кода показано, как представить диалоговое окно, не требующее явного действия пользователя для завершения процесса аутентификации:

Котлин

// Lets the user authenticate without performing an action, such as pressing a
// button, after their biometric credential is accepted.
promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric login for my app")
        .setSubtitle("Log in using your biometric credential")
        .setNegativeButtonText("Use account password")
        .setConfirmationRequired(false)
        .build()

Ява

// Lets the user authenticate without performing an action, such as pressing a
// button, after their biometric credential is accepted.
promptInfo = new BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric login for my app")
        .setSubtitle("Log in using your biometric credential")
        .setNegativeButtonText("Use account password")
        .setConfirmationRequired(false)
        .build();

Разрешить откат к небиометрическим учетным данным

Если вы хотите, чтобы ваше приложение разрешало аутентификацию с использованием биометрических данных или учетных данных устройства, вы можете объявить, что ваше приложение поддерживает учетные данные устройства , включив DEVICE_CREDENTIAL в набор значений, которые вы передаете в setAllowedAuthenticators() .

Если в настоящее время ваше приложение использует createConfirmDeviceCredentialIntent() или setDeviceCredentialAllowed() для предоставления этой возможности, переключитесь на использование setAllowedAuthenticators() .

Дополнительные ресурсы

Чтобы узнать больше о биометрической аутентификации на Android, ознакомьтесь со следующими ресурсами.

Записи в блоге