You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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会抛出KeyPermanentlyInvalidatedException
  • setUserAuthenticationValidityDurationSeconds(-1)表示密钥的授权有效期为单次使用,每次加解密都必须重新进行生物认证
  • 必须使用生物认证返回的CryptoObject中的Cipher来执行加解密,否则会触发"Key user not authenticated"异常

内容的提问来源于stack exchange,提问作者user3740049

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 01:59:50