Android Keystore本地认证加解密异常及指纹变更密钥失效需求
Android Keystore密钥认证异常与指纹变更失效处理方案
问题根源
你遇到的Key user not authenticated异常,核心原因是:
- 密钥设置了
setUserAuthenticationRequired(true)和setUserAuthenticationValidityDurationSeconds(-1),要求每次使用密钥必须经过生物认证授权 - 当前代码在
encryptAndSaveString和decryptSavedString中直接初始化Cipher并调用doFinal,没有使用经过生物认证后的CryptoObject,导致密钥未被系统授权使用 - 同时
KeyPermanentlyInvalidatedException未触发,是因为缺少在生物认证前初始化Cipher以检测密钥失效的逻辑,且getCrypto()方法未实现
修复方案
1. 实现getCrypto()方法,检测密钥失效
在初始化Cipher时捕获KeyPermanentlyInvalidatedException,返回null以触发密钥失效的处理逻辑:
private BiometricPrompt.CryptoObject getCrypto() { try { Cipher cipher = getCipher(); SecretKey key = getKey(); // 初始化Cipher时会检测密钥是否因指纹变更失效 cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key); return new BiometricPrompt.CryptoObject(cipher); } catch (KeyPermanentlyInvalidatedException e) { // 密钥因指纹变更失效,返回null触发后续处理 Log.e("Crypto", "Key permanently invalidated due to biometric change"); return null; } catch (Exception e) { e.printStackTrace(); return null; } }
2. 使用认证后的CryptoObject执行加解密
生物认证成功后,直接使用AuthenticationResult中的Cipher,避免重新初始化:
修改onAuthenticationSucceeded方法:
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU) @Override public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { super.onAuthenticationSucceeded(result); Cipher cipher = result.getCryptoObject().getCipher(); if (cipher == null) { Log.e("Auth", "Cipher not available after authentication"); return; } if(isEncrypt) { encryptWithAuthenticatedCipher(originalData, cipher); }else{ String strDecryptedData = decryptWithAuthenticatedCipher(cipher); Log.d("Decrypted Data @@@", strDecryptedData != null ? strDecryptedData : "Error"); } }
3. 重构加解密方法,依赖认证后的Cipher
替换原有的encryptAndSaveString和decryptSavedString为:
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU) private void encryptWithAuthenticatedCipher(String dataToEncrypt, Cipher cipher) { try { byte[] encryptedBytes = cipher.doFinal(dataToEncrypt.getBytes(StandardCharsets.UTF_8)); byte[] iv = cipher.getIV(); saveIVToPrefs(Base64.encodeToString(iv, Base64.DEFAULT)); saveEncryptedDataToPrefs(Base64.encodeToString(iv, Base64.DEFAULT), Base64.encodeToString(encryptedBytes, Base64.DEFAULT)); Log.d("encrypted Data@@@", getEncryptedDataFromPrefs(ENCRYPTED_DATA_KEY + "_data")); } catch (Exception e) { e.printStackTrace(); } } @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) private String decryptWithAuthenticatedCipher(Cipher cipher) { try { String ivString1 = getEncryptedDataFromPrefs(ENCRYPTED_DATA_KEY + "_iv"); String encryptedDataString = getEncryptedDataFromPrefs(ENCRYPTED_DATA_KEY + "_data"); if (ivString1 != null && encryptedDataString != null) { byte[] encryptedData = Base64.decode(encryptedDataString, Base64.DEFAULT); byte[] decryptedBytes = cipher.doFinal(encryptedData); String decryptedData = new String(decryptedBytes, StandardCharsets.UTF_8); saveDecryptedDataToPrefs(decryptedData); return decryptedData; } } catch (Exception e) { e.printStackTrace(); } return null; }
4. 完善密钥失效后的处理逻辑
在getCrypto()返回null时,删除失效密钥并提示重新生成:
修改按钮点击事件中的逻辑:
// 加密按钮点击 btnBioMetricRegister.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { isEncrypt = true; BiometricPrompt.CryptoObject cryptoObject = getCrypto(); if (cryptoObject != null) { biometricPrompt.authenticate(biometricPromptInfo, cryptoObject); } else { // 密钥失效,删除并提示重新生成 try { KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); keyStore.load(null); keyStore.deleteEntry(KEY_ALIAS); Toast.makeText(context, "密钥已失效,请重新生成", Toast.LENGTH_SHORT).show(); } catch (Exception ex) { ex.printStackTrace(); } } } }); // 解密按钮点击同理 biometricLoginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { isEncrypt = false; BiometricPrompt.CryptoObject cryptoObject = getCrypto(); if (cryptoObject != null) { biometricPrompt.authenticate(biometricPromptInfo, cryptoObject); } else { try { KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); keyStore.load(null); keyStore.deleteEntry(KEY_ALIAS); Toast.makeText(context, "密钥已失效,请重新生成", Toast.LENGTH_SHORT).show(); } catch (Exception ex) { ex.printStackTrace(); } } } });
关键说明
setInvalidatedByBiometricEnrollment(true)参数本身是正确的,当用户添加/删除指纹时,密钥会被标记为失效,此时初始化Cipher会抛出KeyPermanentlyInvalidatedExceptionsetUserAuthenticationValidityDurationSeconds(-1)表示密钥的授权有效期为单次使用,每次加解密都必须重新进行生物认证- 必须使用生物认证返回的
CryptoObject中的Cipher来执行加解密,否则会触发"Key user not authenticated"异常
内容的提问来源于stack exchange,提问作者user3740049
相关产品推荐
相关产品推荐

