Один из методов защиты конфиденциальной информации или премиум-контента в вашем приложении — запрос биометрической аутентификации, например, с помощью распознавания лиц или отпечатков пальцев. В этом руководстве объясняется, как реализовать поддержку биометрической аутентификации в вашем приложении.
Как правило, для первоначального входа на устройство следует использовать Credential Manager . Последующие повторные авторизации можно выполнять либо с помощью Biometric Prompt, либо Credential Manager. Преимущество использования Biometric Prompt заключается в более широких возможностях настройки, в то время как Credential Manager предлагает единую реализацию для обоих процессов.
Укажите типы аутентификации, которые поддерживает ваше приложение.
Чтобы определить типы аутентификации, поддерживаемые вашим приложением, используйте интерфейс BiometricManager.Authenticators
. Система позволяет декларировать следующие типы аутентификации:
-
BIOMETRIC_STRONG
- Аутентификация с использованием биометрических данных класса 3 , как определено на странице определения совместимости Android .
-
BIOMETRIC_WEAK
- Аутентификация с использованием биометрических данных класса 2 , как определено на странице определения совместимости Android .
-
DEVICE_CREDENTIAL
- Аутентификация с использованием учетных данных блокировки экрана — PIN-кода, графического ключа или пароля пользователя.
Чтобы начать использовать аутентификатор, пользователю необходимо создать 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()
.
Отобразить приглашение на вход в систему
Чтобы отобразить системное приглашение с предложением пройти аутентификацию с использованием биометрических данных, используйте библиотеку Biometric . Этот системный диалог един для всех приложений, использующих его, что повышает доверие пользователей. Пример диалога представлен на рисунке 1.
Чтобы добавить биометрическую аутентификацию в ваше приложение с помощью биометрической библиотеки, выполните следующие действия:
В файле
build.gradle
вашего модуля приложения добавьте зависимость от библиотекиandroidx.biometric
.В действии или фрагменте, содержащем диалоговое окно биометрического входа, отобразите диалоговое окно, используя логику, показанную в следующем фрагменте кода:
Котлин
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); }
Аутентификация только с использованием биометрических данных
Если ваше приложение использует секретный ключ, для разблокировки которого требуются биометрические данные, пользователь должен будет подтверждать подлинность своих биометрических данных каждый раз, прежде чем ваше приложение получит доступ к ключу.
Чтобы зашифровать конфиденциальную информацию только после аутентификации пользователя с использованием биометрических данных, выполните следующие действия:
Создайте ключ, использующий следующую конфигурацию
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());
Запустите процесс биометрической аутентификации, включающий шифр:
Котлин
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)); });
В обратных вызовах биометрической аутентификации используйте секретный ключ для шифрования конфиденциальной информации:
Котлин
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-кода, графического ключа или пароля). При настройке этого ключа укажите срок его действия. В течение этого срока ваше приложение может выполнить несколько криптографических операций без необходимости повторной аутентификации пользователя.
Чтобы зашифровать конфиденциальную информацию после аутентификации пользователя с использованием биометрических данных или данных экрана блокировки, выполните следующие действия:
Создайте ключ, использующий следующую конфигурацию
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());
В течение периода времени
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 показаны две версии одного и того же диалогового окна. Одна версия требует явного действия пользователя, а другая — нет.
В следующем фрагменте кода показано, как представить диалоговое окно, не требующее явного действия пользователя для завершения процесса аутентификации:
Котлин
// 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, ознакомьтесь со следующими ресурсами.