Participe do evento ⁠#Android11: apresentação de lançamento da versão Beta no dia 3 de junho.

Exibir uma caixa de diálogo para autenticação biométrica

Um método para proteger informações confidenciais ou conteúdo premium no seu app é solicitar autenticação biométrica, usando reconhecimento facial ou de impressão digital. Este guia explica como fazer com que seu app seja compatível com o login por biometria.

Verificar se a autenticação biométrica está disponível

É possível verificar se um dispositivo é compatível com a autenticação biométrica antes de invocar BiometricPrompt, basta usar o método canAuthenticate() na classe BiometricManager.

O snippet de código a seguir mostra como invocar esse método:

Kotlin

    val biometricManager = BiometricManager.from(this)
    when (biometricManager.canAuthenticate()) {
        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 ->
            Log.e("MY_APP_TAG", "The user hasn't associated " +
            "any biometric credentials with their account.")
    }
    

Java

    BiometricManager biometricManager = BiometricManager.from(this);
    switch (biometricManager.canAuthenticate()) {
        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:
            Log.e("MY_APP_TAG", "The user hasn't associated " +
                "any biometric credentials with their account.");
            break;
    }
    

Exibir a solicitação de login

Para exibir uma solicitação do sistema para autenticação do usuário usando 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.

Captura de tela mostrando a caixa de diálogo
Figura 1. Caixa de diálogo do sistema solicitando autenticação biométrica

Para adicionar a autenticação biométrica ao app usando a biblioteca Biometric, siga as etapas a seguir:

  1. No arquivo app/build.gradle do seu app, adicione uma dependência para a biblioteca Biometric:

        dependencies {
            implementation 'androidx.biometric:biometric:1.0.1'
        }
        
  2. Na atividade ou fragmento 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 informações confidenciais 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 usando um objeto Cipher, o app poderá executar criptografia e descriptografia usando um objeto SecretKey.

As seções a seguir mostram exemplos de como usar um objeto Cipher e um objeto 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 requer 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 confidenciais somente após a autenticação do usuário com credenciais biométricas, siga estas etapas:

  1. Gere uma chave que use a seguinte configuração 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());
        
  2. 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));
        });
        
  3. Nos seus callbacks de autenticação biométrica, use a chave secreta para criptografar as informações confidenciais:

    Kotlin

        override fun onAuthenticationSucceeded(
                result: BiometricPrompt.AuthenticationResult) {
            val encryptedInfo: ByteArray = result.cryptoObject.cipher?.doFinal(
                    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.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 confidenciais depois que da autenticação do usuário autenticar com credenciais biométricas ou de bloqueio de tela, conclua as seguintes etapas:

  1. Gere uma chave que use a seguinte configuração 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)
            .setUserAuthenticationValidityDurationSeconds(VALIDITY_DURATION)
            .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)
            .setUserAuthenticationValidityDurationSeconds(VALIDITY_DURATION)
            .build());
        
  2. Em um período de VALIDITY_DURATION segundos depois da autenticação do usuário, criptografe as informações confidenciais:

    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.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.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 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 se seu app exibe 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á fornecer 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 usando 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.

Captura de tela da caixa de diálogo Captura de tela da caixa de diálogo
Figura 2. Autenticação facial sem confirmação do usuário (acima) e com confirmação do usuário (abaixo).

O snippet de código a seguir 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

    // Allows user to 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

    // Allows user to 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 autorize a autenticação usando credenciais biométricas ou do dispositivo, permita que elas sejam autenticadas usando o PIN, o padrão ou a senha da tela de bloqueio. Para fazer isso, transmita true para o método setDeviceCredentialAllowed().

O snippet de código a seguir mostra como oferecer compatibilidade com autenticação usando credenciais biométricas ou de dispositivo:

Kotlin

    // Allows user to authenticate using their lock screen
    // PIN, pattern, or password.
    promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Biometric login for my app")
            .setSubtitle("Log in using your biometric credential")
            // Cannot call setNegativeButtonText() and
            // setDeviceCredentialAllowed() at the same time.
            // .setNegativeButtonText("Use account password")
            .setDeviceCredentialAllowed(true)
            .build()
    

Java

    // Allows user to authenticate using their lock screen
    // PIN, pattern, or password.
    promptInfo = new BiometricPrompt.PromptInfo.Builder()
            .setTitle("Biometric login for my app")
            .setSubtitle("Log in using your biometric credential")
            // Cannot call setNegativeButtonText() and
            // setDeviceCredentialAllowed() at the same time.
            // .setNegativeButtonText("Use account password")
            .setDeviceCredentialAllowed(true)
            .build();
    

Outros recursos

Para saber mais sobre a autenticação biométrica no Android, consulte os seguintes recursos.

Postagens do blog