原标题:解决在安卓中使用KeyStore和SecretKey使用安全硬件对数据加密解密时明明已请求生物验证,但仍报错:android.security.KeyStoreException: Key user not authenticated的问题
一、问题代码
这是我生成密钥和获取SecretKey 工具类,我的想法时在用户通过身份验证之后获取SecretKey,将SecretKey 存放到本地变量里面,后续就可以继续使用了。
但是这种做法就会抛出我上面提到的那个问题,我原本的排查思路在想是不是我生成的位置不对,到底放在请求生物认证之前还是之后,于是各种办法都试了还问了ChatGPT也没解决掉。
public class CipherHelper {
private static SecretKey cachedSecretKey = null;
/**
* 在设备的 Android 密钥库(AndroidKeyStore)中生成一个随机的 AES 密钥。
* 这个密钥可以用于加密和解密数据,并且该密钥受设备的生物识别(如指纹)保护。
* 这意味着每次要使用这个密钥时,都需要通过生物识别。
*/
@RequiresApi(api = Build.VERSION_CODES.R)
public static void generateSecretKey() {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(new KeyGenParameterSpec.Builder(
CIPHER_KEYSTORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.build());
cachedSecretKey = keyGenerator.generateKey();
} catch (Exception e) {
throw new RuntimeException("无法生成密钥。");
}
}
public static SecretKey getSecretKey() {
if (cachedSecretKey == null) {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
cachedSecretKey = (SecretKey) keyStore.getKey(CIPHER_KEYSTORE_ALIAS, null);
} catch (Exception e) {
throw new RuntimeException("无法获取密钥", e);
}
}
return cachedSecretKey;
}
}
Activity的代码:其中的onCipherStrategyCreated方法是加密策略的选择
/**
* 使用指纹验证进行身份验证
*/
private void authenticateWithFingerprint() {
BiometricPrompt biometricPrompt = new BiometricPrompt(this,
ContextCompat.getMainExecutor(this),
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
Toast.makeText(getApplicationContext(), "指纹认证错误: " + errString, Toast.LENGTH_SHORT).show();
startAppropriateFlow();
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
CipherHelper.generateSecretKey();
Toast.makeText(getApplicationContext(), "指纹认证成功", Toast.LENGTH_SHORT).show();
onCipherStrategyCreated(new FingerprintCipherStrategy(), ENCRYPTION_TYPE_FINGERPRINT);
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Toast.makeText(getApplicationContext(), "指纹认证失败", Toast.LENGTH_SHORT).show();
startAppropriateFlow();
}
});
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.fingerprint_title))
.setSubtitle(getString(R.string.fingerprint_subtitle))
.setNegativeButtonText(getString(R.string.cancel))
.build();
biometricPrompt.authenticate(promptInfo);
}
二、问题原因
然后就没办法了呗,只能去查看官方的文档,毕竟Google和百度都没有好的结果,结果我看到了这样一段话:
文档地址
它的意思是我们通过密钥库,实际上的加密和解密操作是送到执行加密操作的系统进程进行操作的,并没有在我们内存里面。
然后往下翻还有一段话,看到这段话我基本就懂了,使用生物识别的时候加解密应该就是有时效性(或者次数性)的,因为它给到我们一个方法允许我们设置一个指定秒数,在这个指定秒数里面我们可以自由的使用SecretKey加解密,于是我们修改代码
public class CipherHelper {
private static SecretKey cachedSecretKey = null;
/**
* 在设备的 Android 密钥库(AndroidKeyStore)中生成一个随机的 AES 密钥。
* 这个密钥可以用于加密和解密数据,并且该密钥受设备的生物识别(如指纹)保护。
* 这意味着每次要使用这个密钥时,都需要通过生物识别。
*/
@RequiresApi(api = Build.VERSION_CODES.R)
public static void generateSecretKey() {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(new KeyGenParameterSpec.Builder(
CIPHER_KEYSTORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(false)
.setUserAuthenticationParameters(60, KeyProperties.AUTH_BIOMETRIC_STRONG)
.build());
cachedSecretKey = keyGenerator.generateKey();
} catch (Exception e) {
throw new RuntimeException("无法生成密钥。");
}
}
public static SecretKey getSecretKey() {
if (cachedSecretKey == null) {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
cachedSecretKey = (SecretKey) keyStore.getKey(CIPHER_KEYSTORE_ALIAS, null);
} catch (Exception e) {
throw new RuntimeException("授权已过期或无法获取密钥", e);
}
}
return cachedSecretKey;
}
这样设置个参数就解决了异常问题,因为我这个App是没有账号体系的,只使用密钥认证,所以我同时设置了setInvalidatedByBiometricEnrollment在生物认证方式发生变化时使密钥仍然生效,这个默认是true。
然后使用setUserAuthenticationParameters设置有效期60秒,在60秒之后就仍需认证了,这样就解决了我的问题。
版权所有:XuanRan
未经书面授权,禁止转账