24. Keystore与生物识别:BiometricPrompt集成、指纹密钥绑定、人脸识别安全策略
生物识别和Keystore的结合,说白了就是「用你的身体当钥匙」。我最早接触这个功能是在Android 6.0时代,那时候指纹识别刚出来,大家还在纠结怎么把指纹和加密密钥绑在一起。后来Google推出了BiometricPrompt,事情才变得简单起来。
今天我们来聊聊三个核心问题:怎么集成BiometricPrompt、怎么把密钥和生物识别绑定、以及人脸识别有哪些坑要注意。
24.1 BiometricPrompt集成:从入门到实战
BiometricPrompt是Android官方推荐的生物识别API。它统一了指纹、人脸、虹膜等识别方式。你不需要关心底层是哪种传感器,系统会帮你处理。
集成步骤其实不复杂,我习惯分三步走:
- 检查硬件是否可用——先看看设备有没有传感器
- 创建BiometricPrompt实例——配置UI和回调
- 启动认证——调用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);
24.2 指纹密钥绑定:让密钥认主
指纹密钥绑定,核心是「只有通过生物识别验证,才能使用这个密钥」。这需要用到KeyGenParameterSpec的setUserAuthenticationRequired(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);
}
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 避坑指南:我踩过的那些坑
做生物识别集成,有几个坑我印象特别深:
- 密钥失效问题——用户新增指纹后,旧密钥会失效。如果你没有处理好,用户会莫名其妙地无法解密数据。解决方案是监听
KeyStore的onKeyStoreChange事件,或者干脆在新增指纹时重新生成密钥。 - 后台认证问题——BiometricPrompt必须在Activity的前台调用。如果你在Service或BroadcastReceiver里调用,会直接崩溃。我曾经在后台任务里尝试认证,结果用户根本看不到弹窗。
- 多设备兼容问题——不同厂商的人脸识别实现差异很大。华为的3D人脸和三星的2D人脸,API返回的结果可能不一样。建议在测试阶段覆盖主流机型。
- 用户取消后的状态——用户连续取消三次,系统会锁定生物识别。这时候你需要提供备用方案,比如PIN码。
嗯,最后说一句:生物识别是个好东西,但别过度依赖。我见过一些App,只支持指纹登录,结果用户换手机后指纹没了,账号都登不上去。一定要留个后门。
好了,这一章的内容就到这里。生物识别和Keystore的结合,说难不难,说简单也不简单。关键是把「认证」和「密钥」这两个环节理解透,剩下的就是代码细节了。