Google 致力于为黑人社区推动种族平等。查看具体举措

显示生物识别身份验证对话框

如需保护您的应用中的敏感信息或付费内容,一种方法是请求生物识别身份验证,例如使用人脸识别或指纹识别。本指南介绍了如何在您的应用中支持生物识别登录流程。

检查生物识别身份验证是否可用

您可以通过在 BiometricManager 类中使用 canAuthenticate() 方法,在调用 BiometricPrompt 之前检查设备是否支持生物识别身份验证。

以下代码段展示了如何调用此方法:

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;
    }
    

显示登录提示

如需显示请求用户使用生物识别凭据进行身份验证的系统提示,请使用 Biometric 库。这个由系统提供的对话框在使用它的各个应用之间均保持一致,从而打造更值得信赖的用户体验。图 1 中显示了一个示例对话框。

显示对话框的屏幕截图
图 1. 请求生物识别身份验证的系统对话框

如需使用 Biometric 库向应用添加生物识别身份验证,请按照以下步骤操作:

  1. 在应用的 app/build.gradle 文件中,添加 Biometric 库的依赖项:

        dependencies {
            implementation 'androidx.biometric:biometric:1.0.1'
        }
        
  2. 在托管生物识别登录对话框的 Activity 或 Fragment 中,使用以下代码段中所示的逻辑显示对话框:

    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);
            });
        }
        

使用依赖身份验证的加密解决方案

为了进一步保护应用中的敏感信息,您可以使用 CryptoObject 实例将加密技术整合到生物识别身份验证工作流程中。该框架支持以下加密对象:SignatureCipherMac

用户使用生物识别提示成功进行身份验证后,应用即可执行加密操作。例如,如果您使用 Cipher 对象进行身份验证,应用便可以使用 SecretKey 对象执行加密和解密。

以下部分介绍了使用 Cipher 对象和 SecretKey 对象加密数据的示例。每个示例均使用以下方法:

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);
    }
    

仅使用生物识别凭据进行身份验证

如果您的应用使用需要生物识别凭据才能解锁的密钥,每次在应用访问该密钥之前,用户都必须对其生物识别凭据进行身份验证。

要在用户使用生物识别凭据进行身份验证后才加密敏感信息,请完成以下步骤:

  1. 生成使用以下 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. 启动一个包含密码的生物识别身份验证工作流程:

    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. 在生物识别身份验证回调中,使用密钥加密敏感信息:

    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));
        }

使用生物识别或锁定屏幕凭据进行身份验证

您可以采用允许使用生物识别凭据或锁定屏幕凭据(PIN 码、解锁图案或密码)进行身份验证的密钥。配置该密钥时,请指定有效时间段。在该时间段内,您的应用可以执行多项加密操作,而无需用户重新进行身份验证。

如需在用户使用生物识别或锁定屏幕凭据进行身份验证后加密敏感信息,请按照以下步骤操作:

  1. 生成使用以下 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. 在用户完成身份验证后 VALIDITY_DURATION 秒的时间内,对敏感信息进行加密:

    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);
            }
        }
        

无需显式用户操作的身份验证

默认情况下,在接受了用户的生物识别凭据后,系统会要求用户执行特定的操作,例如按某个按钮。如果您的应用显示对话框用以确认敏感或高风险的操作(例如进行购买交易),该配置为首选配置。

但是,如果您的应用针对较低风险的操作显示生物识别身份验证对话框,您可以向系统提供提示,表明用户无需确认身份验证。此提示能够让用户在使用被动模式(如人脸识别或虹膜识别)重新验证身份后,更加快速地查看您的应用中的内容。如需提供此提示,请将 false 传递到 setConfirmationRequired() 方法中。

图 2 展示了同一对话框的两个版本。一个版本需要显式用户操作,另一个版本则不需要。

对话框的屏幕截图 对话框的屏幕截图
图 2. 无需用户确认(上)和需要用户确认(下)的人脸识别身份验证

以下代码段展示了如何显示需要显式用户操作即可完成身份验证流程的对话框:

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();
    

允许回退到非生物识别凭据

如果您希望应用允许使用生物识别或设备凭据进行身份验证,则可以通过将 true 传递到 setDeviceCredentialAllowed() 方法中,允许它们利用屏幕锁定 PIN 码、解锁图案或密码进行身份验证。

以下代码段展示了如何使用生物识别或设备凭据支持身份验证:

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();
    

其他资源

如需详细了解 Android 设备上的生物识别身份验证,请参阅以下资源。

博文