9、Android Biometric API:BiometricPrompt API、BiometricManager、CryptoObject集成、兼容性处理

各位好,今天我们聊聊 Android 生物识别 API 的实战部分。说实话,指纹认证这块坑不少,尤其是 Android 碎片化严重,不同厂商、不同版本的行为差异很大。我个人习惯是把 API 分成三层来理解:能力检测层交互层安全层。这样思路会清晰很多。

9.1 BiometricManager:先问问系统行不行

你想想看,如果手机压根没有指纹模块,或者用户没录入指纹,你直接弹 BiometricPrompt 会怎样?嗯,大概率是崩溃或者无响应。所以第一步,用 BiometricManager 做前置检查。

BiometricManager 是 Android 10 引入的,用来替代旧版的 FingerprintManagerCompat。它的核心方法就一个:canAuthenticate()

BiometricManager manager = BiometricManager.from(context);
int result = manager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG);

switch (result) {
    case BiometricManager.BIOMETRIC_SUCCESS:
        // 可以正常使用
        break;
    case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
        // 设备没有指纹硬件
        break;
    case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
        // 硬件暂时不可用
        break;
    case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
        // 用户没录入指纹
        break;
}

我在项目中遇到过一种情况:某款国产手机,硬件明明支持指纹,但 canAuthenticate() 返回 BIOMETRIC_ERROR_HW_UNAVAILABLE。后来发现是系统服务被冻结了,重启一下就好。所以这个检查结果只能作为参考,不能完全依赖。

小提示:Android 11 及以上,你可以用 Authenticators.BIOMETRIC_WEAK 来支持人脸和指纹混合认证。但说实话,弱生物识别的安全性存疑,我一般只用 BIOMETRIC_STRONG

9.2 BiometricPrompt:真正的认证交互

BiometricPrompt 是 Android 9 推出的统一认证对话框。它替代了旧版的 FingerprintDialog,并且支持指纹、人脸、虹膜等多种方式。说白了,你只需要调用它,系统会帮你处理 UI 和认证流程。

基本用法如下:

BiometricPrompt biometricPrompt = new BiometricPrompt(
    activity,
    executor,
    new BiometricPrompt.AuthenticationCallback() {
        @Override
        public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
            // 认证成功
        }

        @Override
        public void onAuthenticationFailed() {
            // 认证失败(指纹不匹配)
        }

        @Override
        public void onAuthenticationError(int errorCode, CharSequence errString) {
            // 认证错误(比如取消、重试次数过多)
        }
    }
);

BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
    .setTitle("验证指纹")
    .setSubtitle("请触摸指纹传感器")
    .setNegativeButtonText("取消")
    .build();

biometricPrompt.authenticate(promptInfo);

这里有个细节:setNegativeButtonText() 是必须的,否则会抛异常。为什么?因为系统需要给用户一个明确的取消入口。我曾经在测试时忘了加这个,结果直接崩溃,日志提示 Negative button text must be set。嗯,这个坑我替你们踩过了。

注意:BiometricPrompt 的 UI 是系统级的,你无法自定义布局。如果你需要定制化 UI(比如游戏内弹窗),那就得走底层的 FingerprintManager 或者自己封装。但我不建议这么做,安全性和兼容性都会打折扣。

9.3 CryptoObject 集成:安全加密的基石

单纯的指纹认证其实意义不大——你只是验证了「手指是本人的」,但后续的数据保护呢?比如支付场景,你需要用指纹解锁一个密钥,然后用密钥签名交易。这时候就要用到 CryptoObject

CryptoObject 本质上是一个加密对象的容器,可以包装 CipherSignatureMac 等。它的工作流程是这样的:

  1. 生成一个密钥,存入 Android KeyStore(硬件安全模块)
  2. 设置密钥的访问条件:必须经过生物认证才能使用
  3. 创建 Cipher 对象,用密钥初始化
  4. 将 Cipher 包装成 CryptoObject,传给 BiometricPrompt
  5. 认证成功后,用 Cipher 加密/解密数据

代码示例:

// 1. 生成密钥
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
        "my_key",
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        .setUserAuthenticationRequired(true)  // 必须生物认证
        .setInvalidatedByBiometricEnrollment(true)  // 新增指纹时失效
        .build();

KeyGenerator keyGenerator = KeyGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(spec);
keyGenerator.generateKey();

// 2. 创建 Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey("my_key", null);
cipher.init(Cipher.ENCRYPT_MODE, key);

// 3. 包装成 CryptoObject
BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(cipher);

// 4. 认证
biometricPrompt.authenticate(promptInfo, cryptoObject);

这里有个关键点:setInvalidatedByBiometricEnrollment(true)。如果用户新增了一个指纹,之前生成的密钥会立即失效。这是为了防止「旧指纹被删除后,新指纹还能用旧密钥」的安全漏洞。我在项目中见过有人把这个设为 false,结果安全审计没通过。记住,安全功能不是摆设,别偷懒。

核心逻辑:CryptoObject 把「生物认证」和「数据加密」绑定在一起。认证成功才能拿到密钥,认证失败密钥不可用。这样即使攻击者拿到了加密数据,没有你的指纹也解不开。

9.4 兼容性处理:从 Android 6 到 Android 14

Android 生物识别 API 经历了三个阶段:

Android 版本 推荐 API 说明
6.0 (API 23) FingerprintManager 最早版本,需要自己处理 UI
9.0 (API 28) BiometricPrompt (无 CryptoObject) 统一 UI,但加密支持有限
10.0 (API 29) BiometricPrompt + CryptoObject 完整功能,推荐使用
11.0+ (API 30+) BiometricManager + 多模态 支持人脸、虹膜等

兼容性处理的核心策略是:能用新的就别用旧的。但现实是,你的 App 可能还要支持 Android 6 到 8 的设备。这时候怎么办?

我的做法是:

  • 在 Android 9+ 设备上,统一使用 BiometricPrompt
  • 在 Android 6-8 设备上,回退到 FingerprintManagerCompat(来自 support-v4 库)
  • 在 Android 5 及以下,直接提示「设备不支持」

代码判断:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    // 使用 BiometricPrompt
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    // 使用 FingerprintManagerCompat
} else {
    // 不支持
}

这里有个坑:某些厂商在 Android 9 上预装了 FingerprintManager,但 BiometricPrompt 可能不工作。我遇到过一加 6T 的 Android 9 版本,BiometricPrompt 弹不出来,最后发现是系统 ROM 的 bug。解决方案是加一个 try-catch,如果 BiometricPrompt 初始化失败,就降级到 FingerprintManager。

避坑指南:我曾经在华为某款手机上发现,BiometricPrompt 的 onAuthenticationError 回调在用户取消时返回的 errorCode 是 5(BIOMETRIC_ERROR_CANCELED),但小米手机上返回的是 10(BIOMETRIC_ERROR_USER_CANCELED)。所以不要硬编码 errorCode,用系统常量判断。

9.5 整体架构图

下面这张图展示了 Biometric API 的核心调用链路:

Biometric API 调用链路 应用层 (App) BiometricManager BiometricPrompt CryptoObject 系统服务 (FingerprintService) 系统 UI (SystemUI) Android KeyStore 指纹硬件 (Fingerprint HAL) 显示硬件 (Display) TEE 安全环境 图:Biometric API 从应用到硬件的完整调用链路

从图中可以看到,BiometricManager 负责「能不能用」,BiometricPrompt 负责「怎么交互」,CryptoObject 负责「安不安全」。三者各司其职,缺一不可。

9.6 总结

嗯,这一章内容不少。核心就三件事:

  • 先检查:用 BiometricManager 确认硬件和指纹状态
  • 再交互:用 BiometricPrompt 弹出系统认证对话框
  • 最后加密:用 CryptoObject 保护敏感数据

兼容性方面,记住一个原则:能用 BiometricPrompt 就用,不行再降级。别为了兼容旧版本,在新设备上还用 FingerprintManager,那样用户体验会差很多。

最后说一句:指纹认证不是银弹。它只是「用户身份验证」的一个环节,真正的安全性还要靠服务端、传输层、数据存储等多方面配合。别以为加了指纹就万事大吉了。


公众号:蓝海资料掘金营,微信deep3321