Un metodo per proteggere le informazioni sensibili o i contenuti premium all'interno della tua app è richiedere l'autenticazione biometrica, ad esempio utilizzando il riconoscimento facciale o dell'impronta. Questa guida spiega come supportare i flussi di accesso biometrico nella tua app.
Come regola generale, devi utilizzare Gestore delle credenziali per l'accesso iniziale su un dispositivo. Le riautorizzazioni successive possono essere eseguite con la richiesta biometrica o con Credential Manager. Il vantaggio dell'utilizzo del prompt biometrico è che offre più opzioni di personalizzazione, mentre Credential Manager offre un'unica implementazione in entrambi i flussi.
Dichiarare i tipi di autenticazione supportati dalla tua app
Per definire i tipi di autenticazione supportati dalla tua app, utilizza l'interfaccia
BiometricManager.Authenticators
. Il sistema ti consente di dichiarare i seguenti tipi di
autenticazione:
BIOMETRIC_STRONG
- Autenticazione tramite un sistema biometrico di classe 3, come definito nella pagina Definizione di compatibilità Android.
BIOMETRIC_WEAK
- Autenticazione tramite un sistema biometrico di classe 2, come definito nella pagina Definizione di compatibilità Android.
DEVICE_CREDENTIAL
- Autenticazione tramite una credenziale di blocco schermo: il PIN, la sequenza o la password dell'utente.
Per iniziare a utilizzare un autenticatore, l'utente deve creare un PIN, una sequenza o una password. Se l'utente non ne ha già uno, il flusso di registrazione biometrica gli chiede di crearne uno.
Per definire i tipi di autenticazione biometrica accettati dalla tua app, passa un tipo di autenticazione o una combinazione bit a bit di tipi nel metodo setAllowedAuthenticators()
. Il seguente snippet di codice mostra come supportare l'autenticazione utilizzando
credenziali biometriche di Classe 3 o di blocco schermo.
Kotlin
// 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()
Java
// 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();
Le seguenti combinazioni di tipi di autenticatore non sono supportate su
Android 10 (livello API 29) e versioni precedenti: DEVICE_CREDENTIAL
e
BIOMETRIC_STRONG | DEVICE_CREDENTIAL
. Per verificare la presenza di un PIN,
di una sequenza o di una password su Android 10 e versioni precedenti, utilizza il metodo
KeyguardManager.isDeviceSecure()
.
Verificare che l'autenticazione biometrica sia disponibile
Dopo aver deciso quali elementi di autenticazione supporta la tua app, controlla se
questi elementi sono disponibili. A questo scopo, passa la
stessa combinazione bitwise di tipi che hai dichiarato utilizzando il
metodo setAllowedAuthenticators()
nel
metodo canAuthenticate()
.
Se necessario, richiama l'intent
ACTION_BIOMETRIC_ENROLL
. Nell'extra intent, fornisci l'insieme di autenticatori accettati dalla tua app. Questo intent chiede all'utente di registrare le credenziali per un
autenticatore accettato dalla tua app.
Kotlin
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) } }
Java
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; }
Determinare come è stata eseguita l'autenticazione dell'utente
Dopo l'autenticazione dell'utente, puoi verificare se l'autenticazione è stata eseguita utilizzando
una credenziale del dispositivo o una credenziale biometrica chiamando
getAuthenticationType()
.
Visualizzare la richiesta di accesso
Per visualizzare una richiesta di sistema che chiede all'utente di autenticarsi utilizzando credenziali biometriche, utilizza la libreria biometrica. Questa finestra di dialogo fornita dal sistema è coerente in tutte le app che la utilizzano, creando un'esperienza utente più affidabile. Nella Figura 1 viene visualizzata una finestra di dialogo di esempio.
Per aggiungere l'autenticazione biometrica alla tua app utilizzando la libreria Biometric, completa i seguenti passaggi:
Nel file
build.gradle
del modulo dell'app, aggiungi una dipendenza dallaandroidx.biometric
libreria.Nell'attività o nel fragment che ospita la finestra di dialogo di accesso biometrico, visualizza la finestra di dialogo utilizzando la logica mostrata nel seguente snippet di codice:
Kotlin
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) } }
Java
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); }); }
Utilizzare una soluzione crittografica che dipende dall'autenticazione
Per proteggere ulteriormente le informazioni sensibili all'interno della tua app, puoi incorporare
la crittografia nel flusso di lavoro di autenticazione biometrica utilizzando un'istanza di
CryptoObject
.
Il framework supporta i seguenti oggetti crittografici:
Signature
,
Cipher
e
Mac
.
Dopo che l'utente ha eseguito l'autenticazione utilizzando una richiesta biometrica, la tua app può
eseguire un'operazione di crittografia. Ad esempio, se esegui l'autenticazione utilizzando un oggetto Cipher
, la tua app può quindi eseguire la crittografia e la decrittografia utilizzando un oggetto SecretKey
.
Le sezioni seguenti illustrano esempi di utilizzo di un oggetto Cipher
e di un oggetto SecretKey
per criptare i dati. Ogni esempio utilizza i seguenti metodi:
Kotlin
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) }
Java
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); }
Eseguire l'autenticazione utilizzando solo le credenziali biometriche
Se la tua app utilizza una chiave segreta che richiede credenziali biometriche per essere sbloccata, l'utente deve autenticare le proprie credenziali biometriche ogni volta prima che la tua app acceda alla chiave.
Per criptare le informazioni sensibili solo dopo che l'utente si è autenticato utilizzando credenziali biometriche, completa i seguenti passaggi:
Genera una chiave che utilizza la seguente configurazione
KeyGenParameterSpec
:Kotlin
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())
Java
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());
Avvia un flusso di lavoro di autenticazione biometrica che incorpora una cifra:
Kotlin
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)) }
Java
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)); });
All'interno dei callback di autenticazione biometrica, utilizza la chiave segreta per criptare le informazioni sensibili:
Kotlin
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)) }
Java
@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)); }
Esegui l'autenticazione utilizzando le credenziali biometriche o del blocco schermo
Puoi utilizzare una chiave segreta che consente l'autenticazione tramite credenziali biometriche o credenziali della schermata di blocco (PIN, sequenza o password). Quando configuri questa chiave, specifica un periodo di validità. Durante questo periodo di tempo, la tua app può eseguire più operazioni crittografiche senza che l'utente debba autenticarsi di nuovo.
Per criptare le informazioni sensibili dopo l'autenticazione dell'utente tramite credenziali biometriche o della schermata di blocco, completa i seguenti passaggi:
Genera una chiave che utilizza la seguente configurazione
KeyGenParameterSpec
:Kotlin
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())
Java
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());
Entro un periodo di tempo di
VALIDITY_DURATION_SECONDS
dopo l'autenticazione dell'utente, cripta le informazioni sensibili:Kotlin
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) }
Java
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); } }
Esegui l'autenticazione utilizzando le chiavi di autenticazione per utilizzo
Puoi fornire il supporto per le chiavi di autenticazione per utilizzo nella tua istanza di
BiometricPrompt
. Una chiave di questo tipo
richiede all'utente di presentare una credenziale biometrica o una credenziale del dispositivo
ogni volta che l'app deve accedere ai dati protetti da
questa chiave. Le chiavi di autorizzazione per utilizzo possono essere utili per transazioni di alto valore, ad esempio
per effettuare un pagamento di grandi dimensioni o aggiornare le cartelle cliniche di una persona.
Per associare un oggetto BiometricPrompt
a una chiave di autorizzazione per utilizzo, aggiungi un codice
simile al seguente:
Kotlin
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()
Java
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();
Autenticarsi senza un'azione esplicita dell'utente
Per impostazione predefinita, il sistema richiede agli utenti di eseguire un'azione specifica, ad esempio premere un pulsante, dopo l'accettazione delle credenziali biometriche. Questa configurazione è preferibile se la tua app mostra la finestra di dialogo per confermare un'azione sensibile o ad alto rischio, ad esempio un acquisto.
Se la tua app mostra una finestra di dialogo di autenticazione biometrica per un'azione a basso rischio,
tuttavia, puoi fornire un suggerimento al sistema che indica che l'utente non deve
confermare l'autenticazione. Questo suggerimento può consentire all'utente di visualizzare i contenuti nella tua app
più rapidamente dopo la riautenticazione utilizzando una modalità passiva, come il riconoscimento
basato sul volto o sull'iride. Per fornire questo suggerimento, passa false
al metodo
setConfirmationRequired()
.
La figura 2 mostra due versioni della stessa finestra di dialogo. Una versione richiede un'azione esplicita dell'utente, mentre l'altra no.
Il seguente snippet di codice mostra come presentare una finestra di dialogo che non richiede un'azione esplicita dell'utente per completare la procedura di autenticazione:
Kotlin
// 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()
Java
// 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();
Consenti il fallback alle credenziali non biometriche
Se vuoi che la tua app consenta l'autenticazione utilizzando le credenziali biometriche o del dispositivo, puoi dichiarare che la tua app supporta le credenziali del dispositivo includendo DEVICE_CREDENTIAL
nell'insieme di valori che trasmetti a setAllowedAuthenticators()
.
Se la tua app attualmente utilizza
createConfirmDeviceCredentialIntent()
o setDeviceCredentialAllowed()
per fornire questa funzionalità, passa all'utilizzo di setAllowedAuthenticators()
.
Risorse aggiuntive
Per scoprire di più sull'autenticazione biometrica su Android, consulta le seguenti risorse.