顯示生物特徵辨識驗證對話方塊

保護機密資訊或付費內容的方法之一 應用程式要求生物特徵辨識驗證,例如使用臉部辨識或 指紋辨識本指南說明如何在應用程式中支援生物特徵辨識登入流程。

一般來說,您應使用 Credential Manager 在裝置上首次登入。如有需要,您可以使用生物特徵辨識提示、 或 Credential Manager使用生物特徵辨識提示的好處 更多自訂選項,Credential Manager 則單一 並在兩個流程中實作

宣告應用程式支援的驗證類型

如要定義應用程式支援的驗證類型,請使用 BiometricManager.Authenticators 介面。系統可讓您宣告下列幾種驗證類型:

BIOMETRIC_STRONG
使用第 3 級生物特徵辨識 (如 Android 相容性 定義 頁面。
BIOMETRIC_WEAK
使用第 2 級生物特徵辨識 (如 Android 相容性 定義 頁面。
DEVICE_CREDENTIAL
使用螢幕鎖定憑證 (例如使用者的 PIN 碼、解鎖圖案或密碼) 進行驗證。

如要開始使用驗證器,使用者必須建立 PIN 碼、解鎖圖案或密碼。如果使用者尚未建立 PIN 碼、解鎖圖案或密碼,生物特徵辨識註冊機制會提示使用者立即建立。

如要定義應用程式接受的生物特徵辨識驗證類型,請將驗證類型或按位元組合的多種類型傳入 setAllowedAuthenticators() 方法。下列程式碼片段說明如何使用 第 3 級生物特徵辨識或螢幕鎖定憑證。

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

Android 10 (API 級別 29) 以下版本不支援下列驗證器類型組合:DEVICE_CREDENTIALBIOMETRIC_STRONG | DEVICE_CREDENTIAL。如要檢查 PIN 碼是否出現, 解鎖 Android 10 以下版本的解鎖圖案或密碼,並使用 KeyguardManager.isDeviceSecure() 方法。

檢查是否提供生物特徵辨識驗證

決定應用程式支援哪些驗證元素後,請確認應用程式是否支援 可以使用這些元素檢查方式為將使用 setAllowedAuthenticators() 方法宣告的相同位元類型組合傳遞至 canAuthenticate() 方法。如有需要,請叫用 ACTION_BIOMETRIC_ENROLL 意圖動作。在意圖的附加資料中,請提供應用程式接受的驗證器組合。這項意圖會提示使用者為 驗證器。

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

判斷使用者的驗證方式

使用者通過驗證後,您可以使用 取得裝置憑證或生物特徵辨識憑證 getAuthenticationType()

顯示登入提示

如要顯示系統提示,要求使用者使用 生物特徵辨識憑證 生物特徵辨識程式庫。這個 系統提供的對話方塊在使用此功能的所有應用程式中都會保持一致, 提供更可靠的使用者體驗圖 1 顯示的是其中一種對話方塊範例。

顯示對話方塊的螢幕截圖
圖 1. 要求生物特徵辨識的系統對話方塊 驗證機制。

如要使用生物特徵辨識程式庫為應用程式新增生物特徵辨識驗證,請完成下列步驟:

  1. 在應用程式模組的 build.gradle 檔案中,新增 androidx.biometric 程式庫

  2. 在代管生物特徵辨識登入活動的活動或片段中,顯示 這個對話方塊會使用以下程式碼片段所示的邏輯:

    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。 這個架構支援以下加密編譯物件: Signature, CipherMac

使用者透過生物特徵辨識提示順利完成驗證後,應用程式 執行加密編譯作業舉例來說,如果您使用 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 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));
    }

使用生物特徵辨識或螢幕鎖定憑證進行驗證

您可以使用允許透過生物特徵辨識進行驗證的密鑰 憑證或螢幕鎖定憑證 (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)
        .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. 使用者 VALIDITY_DURATION_SECONDS 後的一段時間內 驗證及加密機密資訊:

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

使用單次使用驗證金鑰進行驗證

您可以在 BiometricPrompt 例項中,支援單次使用驗證金鑰。這類索引鍵 要求使用者提供生物特徵辨識憑證或裝置 憑證。 該金鑰單次使用的驗證金鑰適合用於價值高的交易,例如進行大額付款或更新使用者的健康記錄。

如要將 BiometricPrompt 物件與單次使用驗證金鑰建立關聯,請新增如下程式碼:

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

在使用者未明確採取動作的情況下進行驗證

根據預設,使用者必須在系統接受他們的生物特徵辨識憑證之後執行特定動作,例如按下特定按鈕。這個 如果應用程式會顯示對話方塊來確認 敏感或高風險動作,例如購物

如果您的應用程式針對風險較低的動作顯示生物特徵辨識驗證對話方塊, 不過,您可以提示系統使用者 確認驗證。這類提示可讓使用者能夠在透過被動式模態 (例如臉孔或瞳孔辨識) 進行重新驗證後,更快看到應用程式中的內容。如要提供這類提示,請將 false 傳遞至 setConfirmationRequired() 方法。

圖 2 顯示同一個對話方塊的兩個版本。其中一個版本需要使用者明確採取動作,另一個版本則不需要。

對話方塊的螢幕截圖 對話方塊的螢幕截圖
圖 2. 在使用者未確認 (上圖) 及 使用者確認 (下方)。

下列程式碼片段說明如何讓顯示的對話方塊,在「沒有」要求使用者明確採取動作的情況下完成驗證程序:

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

允許採用非生物特徵辨識憑證做為備用驗證方式

如果您想讓應用程式使用生物特徵辨識或裝置進行驗證 憑證,您可以宣告應用程式支援裝置 憑證 其中 DEVICE_CREDENTIAL 位於您傳入的 setAllowedAuthenticators()

如果應用程式目前是使用 createConfirmDeviceCredentialIntent()setDeviceCredentialAllowed() 來提供這項功能,請改用 setAllowedAuthenticators()

其他資源

如要進一步瞭解 Android 系統的生物特徵辨識驗證功能,請參閱下列文章 再複習一下,機構節點 是所有 Google Cloud Platform 資源的根節點

網誌文章