24. Keystore与生物识别:BiometricPrompt集成、指纹密钥绑定、人脸识别安全策略

生物识别和Keystore的结合,说白了就是「用你的身体当钥匙」。我最早接触这个功能是在Android 6.0时代,那时候指纹识别刚出来,大家还在纠结怎么把指纹和加密密钥绑在一起。后来Google推出了BiometricPrompt,事情才变得简单起来。

今天我们来聊聊三个核心问题:怎么集成BiometricPrompt、怎么把密钥和生物识别绑定、以及人脸识别有哪些坑要注意。

24.1 BiometricPrompt集成:从入门到实战

BiometricPrompt是Android官方推荐的生物识别API。它统一了指纹、人脸、虹膜等识别方式。你不需要关心底层是哪种传感器,系统会帮你处理。

集成步骤其实不复杂,我习惯分三步走:

  1. 检查硬件是否可用——先看看设备有没有传感器
  2. 创建BiometricPrompt实例——配置UI和回调
  3. 启动认证——调用authenticate方法

先看第一步,检查硬件:

// 检查设备是否支持生物识别
BiometricManager biometricManager = BiometricManager.from(context);
int canAuthenticate = biometricManager.canAuthenticate(
    BiometricManager.Authenticators.BIOMETRIC_STRONG
);

if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) {
    // 设备支持,可以继续
} else if (canAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
    // 没有硬件,别想了
} else if (canAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
    // 有硬件但没录入指纹/人脸
}

嗯,这里要注意:BIOMETRIC_STRONG表示强生物识别,比如指纹、虹膜。如果你用BIOMETRIC_WEAK,那可能包含一些不太安全的方式,比如2D人脸识别。我个人建议,涉及密钥绑定的场景,一定要用BIOMETRIC_STRONG

第二步,创建BiometricPrompt:

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是必须的,否则会崩溃。另外,Android 11以上支持setConfirmationRequired(false),可以跳过「确认」步骤,用户体验更好。

24.2 指纹密钥绑定:让密钥认主

指纹密钥绑定,核心是「只有通过生物识别验证,才能使用这个密钥」。这需要用到KeyGenParameterSpecsetUserAuthenticationRequired(true)

我曾在项目中遇到一个需求:用户登录后,用指纹加密token,下次打开App直接刷指纹就能登录。实现方式如下:

KeyGenParameterSpec keyGenSpec = new KeyGenParameterSpec.Builder(
    "biometric_key",
    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setUserAuthenticationRequired(true)  // 关键:需要用户认证
    .setInvalidatedByBiometricEnrollment(true)  // 新增指纹时失效
    .build();

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

这里有两个参数很关键:

参数 作用 建议
setUserAuthenticationRequired(true) 必须通过生物识别才能使用密钥 涉及敏感数据时必开
setInvalidatedByBiometricEnrollment(true) 用户新增指纹/人脸时,旧密钥失效 安全要求高时开启

解密时,需要先通过BiometricPrompt认证,然后在回调中获取密钥:

@Override
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
    // 认证成功后,才能获取密钥
    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    keyStore.load(null);
    SecretKey key = (SecretKey) keyStore.getKey("biometric_key", null);
    
    // 用key解密数据
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
    byte[] plaintext = cipher.doFinal(ciphertext);
}
注意:密钥对象只能在BiometricPrompt的回调中使用。如果你在回调外面获取密钥,会抛出IllegalStateException。我曾经踩过这个坑,调试了半天才发现是线程问题。

24.3 人脸识别安全策略:别被一张照片骗了

人脸识别比指纹复杂得多。指纹是物理接触,很难伪造。但人脸识别,尤其是2D摄像头,一张照片就能破解。

Android把人脸识别分成了三个等级:

等级 说明 安全性
BIOMETRIC_STRONG 3D结构光、ToF等硬件级识别
BIOMETRIC_WEAK 2D摄像头+算法
DEVICE_CREDENTIAL PIN码/图案/密码

我的建议是:

  • 涉及支付、敏感数据——必须用BIOMETRIC_STRONG,且绑定密钥
  • 普通解锁、快速登录——可以用BIOMETRIC_WEAK,但别绑定重要密钥
  • 备用方案——始终提供DEVICE_CREDENTIAL作为fallback

为什么人脸识别要特别小心?因为很多国产手机的人脸识别是2D的。你想想看,一张高清照片就能骗过摄像头。所以Google在Android 10之后,强制要求人脸识别必须支持「活体检测」,否则不能算BIOMETRIC_STRONG。

实际开发中,我建议这样配置:

// 只接受强生物识别
BiometricManager biometricManager = BiometricManager.from(context);
int result = biometricManager.canAuthenticate(
    BiometricManager.Authenticators.BIOMETRIC_STRONG
);

// 如果设备只支持弱生物识别,就降级到PIN码
if (result != BiometricManager.BIOMETRIC_SUCCESS) {
    result = biometricManager.canAuthenticate(
        BiometricManager.Authenticators.DEVICE_CREDENTIAL
    );
}

核心原则:生物识别只是「方便」,不是「绝对安全」。密钥绑定才是真正的安全保障。生物识别的作用是「解锁密钥」,而不是「替代密钥」。

24.4 避坑指南:我踩过的那些坑

做生物识别集成,有几个坑我印象特别深:

  • 密钥失效问题——用户新增指纹后,旧密钥会失效。如果你没有处理好,用户会莫名其妙地无法解密数据。解决方案是监听KeyStoreonKeyStoreChange事件,或者干脆在新增指纹时重新生成密钥。
  • 后台认证问题——BiometricPrompt必须在Activity的前台调用。如果你在Service或BroadcastReceiver里调用,会直接崩溃。我曾经在后台任务里尝试认证,结果用户根本看不到弹窗。
  • 多设备兼容问题——不同厂商的人脸识别实现差异很大。华为的3D人脸和三星的2D人脸,API返回的结果可能不一样。建议在测试阶段覆盖主流机型。
  • 用户取消后的状态——用户连续取消三次,系统会锁定生物识别。这时候你需要提供备用方案,比如PIN码。

嗯,最后说一句:生物识别是个好东西,但别过度依赖。我见过一些App,只支持指纹登录,结果用户换手机后指纹没了,账号都登不上去。一定要留个后门。

生物识别与Keystore集成流程 用户 BiometricPrompt 认证成功回调 Android Keystore 关键点 1. 用户触发认证 2. 系统弹出对话框 3. 传感器采集数据 4. 系统验证身份 5. 回调通知结果 6. 获取密钥对象 7. 执行加密/解密 注意: 密钥只能在回调中使用 新增指纹会失效

好了,这一章的内容就到这里。生物识别和Keystore的结合,说难不难,说简单也不简单。关键是把「认证」和「密钥」这两个环节理解透,剩下的就是代码细节了。

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