O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Mostrar caixa de diálogo de 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.

Declarar os tipos de autenticação compatíveis com o app

Para definir os tipos de autenticação compatíveis com seu app, use a interface BiometricManager.Authenticators. O sistema permite que você declare os seguintes tipos de autenticação:

BIOMETRIC_STRONG
Autenticação usando um elemento de hardware que satisfaz o nível de força Forte, conforme definido na página Definição de compatibilidade.
BIOMETRIC_WEAK
Autenticação usando um elemento de hardware que satisfaz o nível de força Fraco, conforme definido na página Definição de compatibilidade.
DEVICE_CREDENTIAL
Autenticação usando uma credencial de bloqueio de tela: o PIN, o padrão ou a senha do usuário.

Para registrar um autenticador, o usuário precisa criar um PIN, padrão ou senha. Se o usuário ainda não tiver um, o fluxo de registro biométrico solicitará que ele crie.

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 a seguir mostra como oferecer compatibilidade com a autenticação por meio de um elemento de hardware "forte" ou uma credencial de bloqueio de tela.

Kotlin

// Allows user to authenticate using either a "strong" hardware element 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")
        // Can't call setNegativeButtonText() and
        // setAllowedAuthenticators(... or DEVICE_CREDENTIAL) at the same time.
        // .setNegativeButtonText("Use account password")
        .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
        .build()

Java

// Allows user to authenticate using either a "strong" hardware element 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")
        // Can't call setNegativeButtonText() and
        // setAllowedAuthenticators(...|DEVICE_CREDENTIAL) at the same time.
        // .setNegativeButtonText("Use account password")
        .setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
        .build();

Observação: algumas combinações de tipos de autenticador não são compatíveis com o Android 10 (API de nível 29) e versões anteriores:

  • DEVICE_CREDENTIAL
  • BIOMETRIC_STRONG | DEVICE_CREDENTIAL

Para verificar a presença de um PIN, padrão ou senha no Android 10 e versões anteriores, use KeyguardManager.isDeviceSecure().

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

Depois de decidir quais elementos de autenticação são compatíveis com o app, verifique se eles estão disponíveis. Para fazer isso, transmita a mesma combinação bit a bit dos tipos declarados anteriormente 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 exibir uma solicitação do sistema para autenticação do usuário por meio de 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 estas etapas:

  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 as 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 com o uso de um objeto Cipher, o app poderá executar criptografia e descriptografia por meio de 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 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 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());
    
  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 da autenticação do usuário com credenciais biométricas ou de bloqueio de tela, conclua as seguintes etapas:

  1. 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());
    
  2. Em um período de VALIDITY_DURATION_SECONDS 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 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 exiba 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 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.

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 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 fornecer 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