Jedną z metod ochrony informacji poufnych lub treści premium w aplikacji jest żądanie uwierzytelniania biometrycznego, np. za pomocą rozpoznawania twarzy lub odcisków palców. Z tego przewodnika dowiesz się, jak obsługiwać logowanie biometryczne w Twojej aplikacji.
Zasadniczo podczas pierwszego logowania na urządzeniu należy użyć Menedżera danych logowania. Kolejne ponowne autoryzacje można wykonać za pomocą promptu biometrycznego lub Menedżera danych logowania. Zaletą promptu biometrycznego jest to, na większą liczbę opcji dostosowania, natomiast i implementacji w obu procesach.
Deklarowanie typów uwierzytelniania obsługiwanych przez aplikację
Aby określić typy uwierzytelniania obsługiwane przez aplikację, użyj parametru
BiometricManager.Authenticators
za pomocą prostego interfejsu online. System umożliwia deklarowanie tych typów uwierzytelniania:
BIOMETRIC_STRONG
- Uwierzytelnianie przy użyciu biometrii klasy 3, zgodnie z definicją Zgodność z Androidem definicja
BIOMETRIC_WEAK
- Uwierzytelnianie przy użyciu biometrii klasy 2, zgodnie z definicją Zgodność z Androidem definicja
DEVICE_CREDENTIAL
- Uwierzytelnianie przy użyciu danych logowania blokady ekranu – kodu PIN, wzoru lub hasła.
Aby zacząć korzystać z aplikacji uwierzytelniającej, użytkownik musi utworzyć kod PIN wzór lub hasło. Jeśli użytkownik nie ma jeszcze konta, proces rejestracji biometrycznej poprosi go o jego utworzenie.
Aby określić typy uwierzytelniania biometrycznego akceptowane przez Twoją aplikację, prześlij
typu uwierzytelniania lub bitowej kombinacji typów do funkcji
setAllowedAuthenticators()
. Poniższy fragment kodu pokazuje, jak obsługiwać uwierzytelnianie za pomocą
danych biometrycznych klasy 3 lub blokady ekranu.
// 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();
Te kombinacje typów uwierzytelniania nie są obsługiwane
Android 10 (poziom interfejsu API 29) i starsze: DEVICE_CREDENTIAL
i
BIOMETRIC_STRONG | DEVICE_CREDENTIAL
Aby sprawdzić, czy został ustawiony kod PIN:
wzoru lub hasła w Androidzie 10 lub starszym,
KeyguardManager.isDeviceSecure()
.
Sprawdź, czy dostępne jest uwierzytelnianie biometryczne
Po wybraniu elementów uwierzytelniania, które obsługuje Twoja aplikacja, sprawdź,
te elementy są dostępne. Aby to zrobić, prześlij tę samą kombinację typów bitowych, którą zadeklarowano za pomocą metody setAllowedAuthenticators()
, do metody canAuthenticate()
.
W razie potrzeby wywołaj metodę
Zamiar ACTION_BIOMETRIC_ENROLL
działania. W dodatkowej intencji podaj zestaw mechanizmów uwierzytelniających używanych przez aplikację
akceptuje. Ta intencja prosi użytkownika o zarejestrowanie danych logowania
akceptowany przez aplikację.
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; }
Określ sposób uwierzytelnienia użytkownika
Po uwierzytelnieniu użytkownika możesz sprawdzić, czy uwierzytelnił się on za pomocą danych logowania na urządzeniu lub danych biometrycznych, wywołując funkcję getAuthenticationType()
.
Wyświetlanie prośby o logowanie
Aby wyświetlić monit systemu z prośbą o uwierzytelnienie przy użyciu danych biometrycznych, użyj Biblioteka biometryczna. Ten wyświetlane przez system okno jest spójne we wszystkich aplikacjach, które go używają, tworząc bardziej godne zaufania. Rysunek 1 przedstawia przykładowe okno.
Aby dodać uwierzytelnianie biometryczne do aplikacji za pomocą biblioteki biometrycznej:
W pliku
build.gradle
modułu aplikacji dodaj zależnośćandroidx.biometric
Biblioteka.We fragmencie aktywności lub fragmencie, który zawiera okno logowania biometrycznego, wyświetl w oknie, korzystając z logiki widocznej w tym fragmencie kodu:
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); }); }
Użyj rozwiązania kryptograficznego, które zależy od uwierzytelniania.
Aby jeszcze lepiej chronić informacje poufne w aplikacji, możesz uwzględnić w niej
kryptografii w procesie uwierzytelniania biometrycznego przy użyciu instancji
CryptoObject
Platforma obsługuje te obiekty kryptograficzne: Signature
, Cipher
i Mac
.
Gdy użytkownik uwierzytelni się przy użyciu promptu biometrycznego, aplikacja może
do przeprowadzenia operacji kryptograficznej. Jeśli na przykład uwierzytelniasz się za pomocą obiektu Cipher
, Twoja aplikacja może szyfrować i odszyfrowywać za pomocą obiektu SecretKey
.
W poniższych sekcjach znajdziesz przykłady użycia obiektu Cipher
oraz
SecretKey
obiekt do szyfrowania danych. Każdy przykład korzysta z tych metod:
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); }
Uwierzytelniaj tylko przy użyciu danych biometrycznych
Jeśli Twoja aplikacja używa tajnego klucza, który do odblokowywania wymaga danych biometrycznych, użytkownik musi uwierzytelniać swoje dane biometryczne za każdym razem przed rozpoczęciem korzystania z aplikacji uzyskuje dostęp do klucza.
Aby szyfrować informacje poufne tylko wtedy, gdy użytkownik uwierzytelni się za pomocą danych logowania biometrycznego, wykonaj te czynności:
Wygeneruj klucz używający tych elementów
KeyGenParameterSpec
Konfiguracja: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());Rozpocznij proces uwierzytelniania biometrycznego, który obejmuje mechanizm szyfrowania:
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)); });
W wywołaniach funkcji uwierzytelniania biometrycznego użyj klucza tajnego do zaszyfrowania informacji poufnych:
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)); }
uwierzytelniać się za pomocą danych biometrycznych lub danych logowania do ekranu blokady;
Możesz użyć tajnego klucza, który umożliwia uwierzytelnianie za pomocą biometrii dane logowania lub dane logowania na ekranie blokady (kod PIN, wzór lub hasło). Kiedy podczas konfigurowania tego klucza, określ okres ważności. W tym czasie aplikacja może wykonywać wiele operacji kryptograficznych bez konieczności ponownego uwierzytelniania użytkownika.
do zaszyfrowania informacji poufnych po uwierzytelnieniu użytkownika za pomocą uwierzytelniania biometrycznego lub dane logowania na ekran blokady, wykonaj te czynności:
Wygeneruj klucz, który używa tej konfiguracji:
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());W okresie
VALIDITY_DURATION_SECONDS
po tym, jak użytkownik uwierzytelnia, szyfruje informacje poufne: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); } }
Uwierzytelnianie przy użyciu kluczy uwierzytelniających na potrzeby pojedynczego użycia
Możesz zapewnić obsługę kluczy autoryzujących w ramach instancji BiometricPrompt
. Taki klucz
wymaga od użytkownika przedstawienia danych logowania biometrycznych lub urządzenia
danych logowania za każdym razem, gdy aplikacja potrzebuje dostępu do danych chronionych przez
ten klucz. Klucze uwierzytelniające mogą być przydatne w przypadku transakcji o wysokiej wartości, takich jak dokonywanie dużych płatności lub aktualizowanie danych medycznych.
Aby powiązać obiekt BiometricPrompt
z kluczem auth-per-use, dodaj kod
podobnie jak poniżej:
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();
Uwierzytelnij bez wyraźnego działania użytkownika
Domyślnie system wymaga od użytkownika wykonania określonej czynności. Na przykład: naciskając przycisk po zaakceptowaniu danych biometrycznych. Ta konfiguracja jest preferowana, jeśli aplikacja wyświetla okno dialogowe, aby potwierdzić działanie o charakterze wrażliwym lub obarczonym wysokim ryzykiem, np. dokonanie zakupu.
Jeśli aplikacja wyświetla okno uwierzytelniania biometrycznego dla działania o mniejszym ryzyku,
możesz jednak zapewnić systemowi wskazówkę, że użytkownik nie musi
potwierdzić uwierzytelnianie. Ten podpowiedź może pozwolić użytkownikowi szybciej wyświetlić treści w aplikacji po ponownym uwierzytelnieniu za pomocą metody pasywnej, takiej jak rozpoznawanie twarzy lub tęczówki. Aby ją zastosować, przekaż false
do
Metoda setConfirmationRequired()
.
Rysunek 2 przedstawia 2 wersje tego samego okienka. Jedna wersja wymaga wyraźnego działania użytkownika, a druga – nie.
Ten fragment kodu pokazuje, jak wyświetlić okno, które nie wymagają wyraźnego działania użytkownika w celu ukończenia procesu uwierzytelniania:
// 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();
Zezwalaj na korzystanie z zastępczych danych logowania niebiometrycznych
Jeśli aplikacja ma zezwalać na uwierzytelnianie biometryczne lub przy użyciu urządzenia
dane logowania, możesz zadeklarować, że aplikacja obsługuje urządzenia
dane logowania, dodając
DEVICE_CREDENTIAL
w zbiorze wartości, które przekazujesz do
setAllowedAuthenticators()
Jeśli Twoja aplikacja obecnie używa
createConfirmDeviceCredentialIntent()
lub setDeviceCredentialAllowed()
aby udostępnić tę funkcję, przełącz się na używanie setAllowedAuthenticators()
.
Dodatkowe materiały
Więcej informacji o uwierzytelnianiu biometrycznym na urządzeniach z Androidem znajdziesz w tych materiałach.