Um método para proteger informações confidenciais ou conteúdo premium no app é solicitar a autenticação biométrica, usando reconhecimento facial ou de impressão digital. Este guia explica como fazer com que o app ofereça suporte ao fluxo de login por biometria.
Como regra geral, use o Gerenciador de credenciais para o login inicial em um dispositivo. Reautorizações subsequentes podem ser feitas com o comando biométrico ou o Gerenciador de credenciais. A vantagem do uso do comando de biometria é que ele oferece mais opções de personalização, enquanto o Gerenciador de credenciais oferece uma única implementação nos dois fluxos.
Declarar os tipos de autenticação com suporte do app
Para definir os tipos de autenticação disponíveis no seu app, use a
interface
BiometricManager.Authenticators
. O sistema permite que você declare estes tipos de
autenticação:
BIOMETRIC_STRONG
- Autenticação usando uma biometria de Classe 3, conforme definido na página Definição de compatibilidade do Android.
BIOMETRIC_WEAK
- Autenticação usando uma biometria de Classe 2, conforme definido na página Definição de compatibilidade do Android.
DEVICE_CREDENTIAL
- Autenticação usando uma credencial de bloqueio de tela: o PIN, o padrão ou a senha do usuário.
Para começar a usar um autenticador, o usuário precisa criar um PIN, padrão ou senha. Se o usuário ainda não tiver uma dessas opções de bloqueio, o fluxo de registro biométrico vai solicitar que ele escolha uma delas para usar.
Para definir os tipos de autenticação biométrica que o app aceita, transmita um
tipo de autenticação ou uma combinação bit a bit de tipos para o
método
setAllowedAuthenticators()
. O snippet de código abaixo mostra como oferecer suporte à autenticação usando
uma credencial biométrica de Classe 3 ou um bloqueio de tela.
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();
As combinações de tipos de autenticador abaixo não têm suporte no
Android 10 (nível 29 da API) e versões anteriores: DEVICE_CREDENTIAL
e
BIOMETRIC_STRONG | DEVICE_CREDENTIAL
. Para conferir se o dispositivo tem um PIN,
padrão ou senha no Android 10 e versões anteriores, use o
método
KeyguardManager.isDeviceSecure()
.
Verificar se a autenticação biométrica está disponível
Depois de decidir quais elementos de autenticação vão ter suporte do app, verifique se
eles estão disponíveis. Para fazer isso, transmita a
mesma combinação bit a bit de tipos declarados usando o
método setAllowedAuthenticators()
para o
método canAuthenticate()
.
Se necessário, invoque a
ação da intent
ACTION_BIOMETRIC_ENROLL
. Na intent extra, forneça o conjunto de autenticadores que o app aceita. Essa intent solicita que o usuário registre as credenciais de um
autenticador aceito pelo 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; }
Determinar como o usuário foi autenticado
Depois de fazer a autenticação, é possível verificar se o usuário foi autenticado usando
uma credencial de dispositivo ou uma credencial biométrica chamando
getAuthenticationType()
.
Exibir a solicitação de login
Para mostrar uma solicitação do sistema para autenticação do usuário por credenciais biométricas, use a biblioteca Biometric. Essa caixa de diálogo do sistema é consistente em todos os apps que a utilizam, o que gera uma experiência mais confiável para o usuário. Um exemplo de caixa de diálogo é apresentado na Figura 1.
Para adicionar a autenticação biométrica ao app usando a biblioteca Biometric, siga estas etapas:
No arquivo
build.gradle
do módulo do app, adicione uma dependência na bibliotecaandroidx.biometric
.No fragmento ou atividade que hospeda a caixa de diálogo para login por biometria, mostre a caixa de diálogo usando a lógica apresentada no snippet de código a seguir:
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); }); }
Usar uma solução criptográfica dependente da autenticação
Para proteger ainda mais as informações sensíveis no seu app, você pode incorporar
criptografia no fluxo de trabalho de autenticação biométrica usando uma instância de
CryptoObject
.
O framework é compatível com os seguintes objetos criptográficos:
Signature
,
Cipher
e
Mac
.
Depois que o usuário faz a autenticação usando a biometria, seu app pode
executar uma operação criptográfica. Por exemplo, se você autenticar com o uso de um
objeto Cipher
, o app vai poder executar criptografia e descriptografia com um
objeto SecretKey
.
As seções a seguir mostram exemplos de como usar os objetos Cipher
e
SecretKey
para criptografar dados. Cada exemplo usa os seguintes
métodos:
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); }
Autenticar usando apenas credenciais biométricas
Se o app usar uma chave secreta que exige credenciais biométricas para ser desbloqueada, o usuário precisará autenticar as credenciais biométricas todas as vezes antes que o app acesse a chave.
Para criptografar informações sensíveis somente após a autenticação do usuário com credenciais biométricas, siga estas etapas:
Gere uma chave que use a seguinte configuração de
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());
Inicie um fluxo de trabalho de autenticação biométrica que incorpore uma criptografia:
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)); });
Nos seus callbacks de autenticação biométrica, use a chave secreta para criptografar as informações sensíveis:
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)); }
Autenticar usando biometria ou credenciais da tela de bloqueio
Você pode usar uma chave secreta que permita a autenticação usando credenciais biométricas ou credenciais da tela de bloqueio (PIN, padrão ou senha). Ao configurar essa chave, especifique um período de validade. Durante esse período, o app pode executar várias operações criptográficas sem que o usuário precise fazer a autenticação novamente.
Para criptografar informações sensíveis depois da autenticação do usuário com credenciais biométricas ou de bloqueio de tela, conclua estas etapas:
Gere uma chave que use a seguinte configuração de
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());
Em um período de
VALIDITY_DURATION_SECONDS
depois da autenticação do usuário, criptografe as informações sensíveis: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); } }
Autenticar com chaves de autenticação ao uso
É possível oferecer compatibilidade com chaves de autenticação ao uso na instância de
BiometricPrompt
. Essas chaves
exigem que o usuário apresente uma credencial biométrica ou uma credencial
do dispositivo toda vez que o app precisar acessar dados protegidos
por essa chave. As chaves de autenticação ao uso podem ser úteis para transações de alto valor, como
efetuar um pagamento de valor elevado ou atualizar os registros médicos de uma pessoa.
Para associar um objeto BiometricPrompt
a uma chave de autenticação ao uso, adicione um código
semelhante a este:
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();
Autenticar sem ação explícita do usuário
Por padrão, depois de aceitar as credenciais biométricas, o sistema solicita que o usuário realize uma ação específica, como apertar um botão. É preferível usar essa configuração caso seu app mostre caixas de diálogo para confirmar ações confidenciais ou de alto risco, como uma compra.
No entanto, se o app mostrar uma caixa de diálogo de autenticação biométrica para uma ação
de menor risco, você poderá dar uma dica ao sistema de que o usuário não precisa
confirmar a autenticação. Essa dica pode permitir que o usuário visualize o conteúdo no app
de forma mais rápida, depois de repetir a autenticação com o uso de uma modalidade passiva, como
reconhecimento facial ou de íris. Para fornecer essa dica, transmita false
para o
método setConfirmationRequired()
.
A figura 2 mostra duas versões da mesma caixa de diálogo. Uma delas exige uma ação explícita do usuário, enquanto a outra não.
O snippet de código abaixo mostra como apresentar uma caixa de diálogo que não exige uma ação explícita do usuário para realizar o processo de autenticação:
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();
Permitir substituto para credenciais não biométricas
Se você quiser que seu app permita a autenticação usando credenciais biométricas ou do
dispositivo, declare que o app é compatível com credenciais
do dispositivo. Para isso, inclua
DEVICE_CREDENTIAL
no conjunto de valores que você transmite para
setAllowedAuthenticators()
.
Caso seu app use
createConfirmDeviceCredentialIntent()
ou setDeviceCredentialAllowed()
atualmente para oferecer esse recurso, comece a usar setAllowedAuthenticators()
.
Outros recursos
Para saber mais sobre a autenticação biométrica no Android, consulte os seguintes recursos.
Postagens do blog
- Como migrar de FingerprintManager para BiometricPrompt (link em inglês)