使用BIOMETRIC_WEAK存储AndroidKeyStore时遇UserNotAuthenticated错误
问题分析
核心原因是使用BIOMETRIC_WEAK时,BiometricPrompt认证未关联CryptoObject。Android Keystore明确要求:任何需要用户认证才能调用的密钥,必须在生物识别认证流程中通过CryptoObject将密钥操作与认证行为绑定,这样Keystore才会生成对应的认证令牌,允许后续的加密/解密操作。
你的代码中,BIOMETRIC_STRONG分支会生成Cipher并传入CryptoObject完成认证,但BIOMETRIC_WEAK分支直接调用biometricPrompt.authenticate(promptInfo),没有关联密钥操作,导致Keystore未收到认证令牌,后续使用密钥加密时就会抛出Key user not authenticated错误。
指纹能正常工作属于部分设备的兼容特例(部分厂商对指纹认证的令牌处理更宽松),但面部识别作为BIOMETRIC_WEAK的典型场景,严格遵循了Keystore的认证绑定要求,因此触发了规范报错。
解决方案
修改launchBiometricPromptEncrypt方法,统一为所有认证类型(包括BIOMETRIC_WEAK)绑定CryptoObject,移除STRONG和WEAK的分支判断:
public static void launchBiometricPromptEncrypt(BiometricPromptEncryptListener listener, Context context, String keyAlias, char[] value, int validationTimeout, BiometricPrompt.PromptInfo promptInfo) { ShowBiometricPromptActivityListener showBiometricPromptlistener = new ShowBiometricPromptActivityListener() { @Override public void onSuccessful() { BiometricPromptListener biometricPromptListener = new BiometricPromptListener() { @Override public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { try { // 直接使用认证结果中绑定好令牌的Cipher,禁止重新初始化 Cipher cipher = result.getCryptoObject().getCipher(); String dat = CryptographicUtils.encryptData(value, cipher); String iv = CryptographicUtils.getIV(cipher); listener.onFinished(dat, iv); } catch (Exception ex) { listener.onError(new SiaKeystoreInternalError(SiaKeystoreInternalError.KEYSTORE_INTERNAL_ERROR, "", ex.getMessage())); } } @Override public void onError(SiaKeystoreInternalError error) { listener.onError(error); } }; BiometricPrompt biometricPrompt = generateBiometricPrompt(biometricPromptListener, context); try { // 统一生成Cipher并绑定CryptoObject,所有认证类型共用此逻辑 Cipher cipher = CryptographicUtils.getEncryptCipher(context, keyAlias, true, validationTimeout); BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(cipher); biometricPrompt.authenticate(promptInfo, cryptoObject); } catch ( Exception e) { NavigationUtils.finishHostTransparentFragmentActivity(); listener.onError(new SiaKeystoreInternalError(SiaKeystoreInternalError.KEYSTORE_INTERNAL_ERROR, "", e.getMessage())); } } @Override public void onError(SiaKeystoreInternalError error) { listener.onError(error); } }; showBiometricPromptActivity(showBiometricPromptlistener, context, promptInfo); }
额外注意事项
- 禁止重新初始化Cipher:认证成功后必须直接使用
result.getCryptoObject().getCipher(),重新调用getEncryptCipher会生成未绑定认证令牌的Cipher,仍会触发相同错误。 - 密钥参数一致性:生成密钥时的
userAuthenticationValidityDurationSeconds参数,要和认证时传入的validationTimeout保持一致,确保密钥有效时长配置匹配。 - StrongBox兼容性:当前代码中
setIsStrongBoxBacked的逻辑无需修改,设备支持StrongBox时会自动适配,不影响认证绑定流程。
内容的提问来源于stack exchange,提问作者Víctor Martín
相关产品推荐
相关产品推荐

