Pour protéger des informations sensibles ou du contenu premium dans votre application, une méthode consiste à demander une authentification biométrique ; en utilisant la reconnaissance faciale ou la reconnaissance d'empreintes digitales, par exemple. Ce guide vous explique comment prendre en charge les flux de connexion biométrique dans votre application.
En règle générale, vous devez utiliser le Gestionnaire d'identifiants pour la connexion initiale sur un appareil. Les réautorisations ultérieures peuvent être effectuées à l'aide de l'invite biométrique ou du Gestionnaire d'identifiants. L'avantage d'utiliser l'invite biométrique est qu'elle offre plus d'options de personnalisation, tandis que le Gestionnaire d'identifiants propose une seule implémentation pour les deux flux.
Déclarer les types d'authentification compatibles avec votre application
Pour définir les types d'authentification compatibles avec votre application, utilisez l'interface BiometricManager.Authenticators
. Le système vous permet de déclarer les types d'authentification suivants :
BIOMETRIC_STRONG
- Authentification via une biométrie de classe 3, telle que définie sur la page Définition de compatibilité Android.
BIOMETRIC_WEAK
- Authentification via une biométrie de classe 2, telle que définie sur la page Définition de compatibilité Android.
DEVICE_CREDENTIAL
- Authentification via des identifiants de verrouillage de l'écran (code, schéma ou mot de passe de l'utilisateur).
Pour commencer à utiliser un authentificateur, l'utilisateur doit créer un code, un schéma ou un mot de passe. S'il n'en a pas encore défini, le flux d'enregistrement biométrique l'invite à en créer un.
Pour définir les types d'authentification biométrique acceptés par votre application, transmettez un type d'authentification ou une combinaison de types au niveau du bit à la méthode setAllowedAuthenticators()
. L'extrait de code suivant montre comment prendre en charge l'authentification à l'aide d'une biométrie de classe 3 ou d'identifiants de verrouillage de l'écran.
// 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();
Remarque : Les combinaisons de types d'authentificateur suivantes ne sont pas compatibles avec Android 10 (niveau d'API 29) ou version antérieure : DEVICE_CREDENTIAL
et BIOMETRIC_STRONG | DEVICE_CREDENTIAL
. Pour vérifier la présence d'un code, d'un schéma ou d'un mot de passe sous Android 10 ou version antérieure, utilisez la méthode KeyguardManager.isDeviceSecure()
.
Vérifier que l'authentification biométrique est disponible
Une fois que vous avez choisi les éléments d'authentification pris en charge par votre application, vérifiez s'ils sont disponibles. Pour ce faire, transmettez la même combinaison de types au niveau du bit que celle que vous avez déclarée à l'aide de la méthode setAllowedAuthenticators()
dans la méthode canAuthenticate()
.
Si nécessaire, appelez l'action d'intent ACTION_BIOMETRIC_ENROLL
. Dans l'extra d'intent, fournissez l'ensemble d'authentificateurs accepté par votre application. Cet intent invite l'utilisateur à enregistrer les identifiants d'un authentificateur accepté par votre application.
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;
}
Déterminer la méthode d'authentification de l'utilisateur
Une fois l'utilisateur authentifié, vous pouvez vérifier s'il a utilisé des identifiants d'appareil ou des identifiants biométriques en appelant getAuthenticationType()
.
Afficher l'invite de connexion
Pour afficher une invite système qui demande à l'utilisateur de s'authentifier à l'aide d'identifiants biométriques, utilisez la bibliothèque Biometric. Cette boîte de dialogue fournie par le système est uniforme dans toutes les applications qui l'utilisent, ce qui contribue à améliorer l'expérience utilisateur. La figure 1 montre un exemple de boîte de dialogue.
Pour ajouter l'authentification biométrique à votre application à l'aide de la bibliothèque Biometric, procédez comme suit :
Dans le fichier
build.gradle
de votre module d'application, ajoutez une dépendance à la bibliothèqueandroidx.biometric
.Dans l'activité ou le fragment qui héberge la boîte de dialogue de connexion biométrique, affichez cette boîte de dialogue en utilisant la logique illustrée dans l'extrait de code suivant :
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);
});
}
Utiliser une solution cryptographique qui dépend de l'authentification
Pour mieux protéger les informations sensibles au sein de votre application, vous pouvez intégrer la cryptographie à votre workflow d'authentification biométrique à l'aide d'une instance de CryptoObject
.
Le framework accepte les objets cryptographiques suivants : Signature
, Cipher
et Mac
.
Une fois que l'utilisateur s'est authentifié à l'aide d'une invite biométrique, votre application peut effectuer une opération cryptographique. Par exemple, si vous vous authentifiez à l'aide d'un objet Cipher
, votre application peut ensuite effectuer le chiffrement et le déchiffrement à l'aide d'un objet SecretKey
.
Les sections suivantes présentent des exemples d'utilisation d'un objet Cipher
et d'un objet SecretKey
pour chiffrer des données. Dans chaque exemple, les méthodes suivantes sont utilisées :
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);
}
S'authentifier en utilisant uniquement des identifiants biométriques
Si votre application utilise une clé secrète dont le déverrouillage nécessite des identifiants biométriques, l'utilisateur doit, à chaque fois, authentifier ses identifiants biométriques avant que votre application n'accède à la clé.
Pour que les informations sensibles soient chiffrées uniquement après que l'utilisateur s'est authentifié à l'aide d'identifiants biométriques, procédez comme suit :
Générez une clé qui utilise la configuration
KeyGenParameterSpec
suivante :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());Démarrez un workflow d'authentification biométrique qui comprend un algorithme de chiffrement :
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));
});Dans vos rappels d'authentification biométrique, chiffrez les informations sensibles à l'aide de la clé secrète :
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));
}
S'authentifier à l'aide d'identifiants biométriques ou d'identifiants d'écran de verrouillage
Vous pouvez utiliser une clé secrète pour l'authentification à l'aide d'identifiants biométriques ou d'identifiants d'écran de verrouillage (code, schéma ou mot de passe). Lors de la configuration de cette clé, spécifiez une période de validité. Au cours de cette période, votre application peut effectuer plusieurs opérations cryptographiques sans que l'utilisateur n'ait à s'authentifier à nouveau.
Pour chiffrer des informations sensibles une fois que l'utilisateur s'est authentifié à l'aide d'identifiants biométriques ou d'écran de verrouillage, procédez comme suit :
Générez une clé qui utilise la configuration
KeyGenParameterSpec
suivante :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());Dans un délai de
VALIDITY_DURATION_SECONDS
après l'authentification de l'utilisateur, chiffrez les informations sensibles :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);
}
}
S'authentifier à l'aide de clés auth-per-use (authentification à l'utilisation)
Vous pouvez assurer la prise en charge des clés auth-per-use dans votre instance de BiometricPrompt
. Une clé de ce type oblige l'utilisateur à présenter un identifiant biométrique ou un identifiant d'appareil chaque fois que votre application doit accéder aux données protégées par cette clé. Les clés auth-per-use peuvent être utiles pour les opérations importantes, telles qu'effectuer un gros paiement ou mettre à jour le dossier médical d'une personne, par exemple.
Pour associer un objet BiometricPrompt
à une clé auth-per-use, ajoutez un code semblable à celui-ci :
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();
S'authentifier sans action explicite de l'utilisateur
Par défaut, le système exige que les utilisateurs effectuent une action spécifique (comme appuyer sur un bouton) une fois que leurs identifiants biométriques ont été acceptés. Cette configuration est préférable si votre application affiche une boîte de dialogue pour confirmer une action sensible ou à haut risque, comme effectuer un achat.
Si votre application affiche une boîte de dialogue d'authentification biométrique pour une action à faible risque, vous pouvez indiquer au système qu'il n'est pas nécessaire de confirmer l'authentification. Cet indice permet à l'utilisateur d'afficher le contenu de votre application plus rapidement après s'être authentifié à nouveau à l'aide d'une modalité passive, telle que la reconnaissance du visage ou de l'iris. Pour fournir cet indice, transmettez false
à la méthode setConfirmationRequired()
.
La figure 2 montre deux versions de la même boîte de dialogue. Dans un cas, une action explicite de l'utilisateur est requise et dans l'autre non.
L'extrait de code suivant montre comment afficher une boîte de dialogue qui n'exige pas d'action explicite de l'utilisateur pour mener à bien le processus d'authentification :
// 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();
Autoriser le retour à des identifiants non biométriques
Si vous souhaitez que votre application autorise l'authentification à l'aide d'identifiants biométriques ou d'identifiants d'appareil, vous pouvez déclarer qu'elle accepte les identifiants d'appareil en insérant DEVICE_CREDENTIAL
dans l'ensemble de valeurs que vous transmettez à setAllowedAuthenticators()
.
Si votre application utilise actuellement createConfirmDeviceCredentialIntent()
ou setDeviceCredentialAllowed()
pour fournir cette fonctionnalité, passez à setAllowedAuthenticators()
.
Ressources supplémentaires
Pour en savoir plus sur l'authentification biométrique sur Android, consultez les ressources suivantes.
Articles de blog
- Migrating from FingerprintManager to BiometricPrompt (Migrer de FingerprintManager vers BiometricPrompt)