问题描述:使用BiometricPrompt认证访问Android KeyStore时,每次需要对KeyStore进行读写操作时都会出现BiometricPrompt。我正在寻找一种解决方案,只进行一次身份验证,然后按照我的意愿操作密钥库中的数据,其方式与在 iOS KeyChain 上完成的方式类似。
我已经为 Android 实现了生物特征认证,因此我可以通过在 KeyStore 参数构建器上调用 setUserAuthenticationRequired(true) 将应用程序 API AuthRefreshToken 存储在由生物特征认证保护的应用程序 KeyStore 中(见下文)。我遵循了 Google ( https://github.com/android/security-samples/tree/main/BiometricLoginKotlin ) 和其他开发人员的示例,并使解决方案成功运行。我现在正在尝试在第二个工作日解决上述问题,但没有成功,现在考虑在没有 CryptoObject 的情况下使用 BiometricPrompt,这将是一个很大的失望。我怀疑有一种方法可以在一段时间内进行一次身份验证,也许是通过设置paramsBuilder.setUserAuthenticationValidityDurationSeconds(30)
,但我无法达到预期的结果。
要访问 KeyStore 并读取 API AuthRefreshToken,我使用以下代码:
biometricPrompt = BiometricPromptUtils.createBiometricPrompt(this, ::decryptServerTokenFromStorage)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
fun decryptServerTokenFromStorage(authResult: BiometricPrompt.AuthenticationResult) {
ciphertextWrapper?.let { textWrapper ->
authResult.cryptoObject?.cipher?.let {
val authRefreshToken = cryptographyManager.decryptData(textWrapper.ciphertext, it)
// Use authRefreshToken to get authToken from the API
// The API returns new authRefreshToken which has to be saved back to the KeyStore
}
}
}
一切正常,我得到了解密的数据。但是,在使用 AuthRefreshToken 对应用程序 API 进行每次身份验证后,令牌会发生变化,我必须立即将其保存回 KeyStore。发生这种情况时,我使用下面的代码,它再次显示 BiometricPrompt。这会导致 UI 流显示 BiometricPrompt 两次:
biometricPrompt = BiometricPromptUtils.createBiometricPrompt(this, ::encryptServerTokenToStorage)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
fun encryptServerTokenToStorage(authResult: BiometricPrompt.AuthenticationResult) {
authResult.cryptoObject?.cipher?.apply {
SampleAppUser.refreshAuthToken?.let { refreshAuthToken ->
Log.d(TAG, "The token from server is $refreshAuthToken")
val encryptedServerTokenWrapper = cryptographyManager.encryptData(refreshAuthToken, this)
// Now save encrypted authRefreshToken together with initializationVector in the app prefs for future authentications
)
}
}
}
如何使用 BiometricPrompt 一次进行身份验证,以便在不多次调用 BiometricPrompt 的情况下对 KeyStore 具有完整的读/写访问权限,例如 1 分钟或更长时间?
我尝试了不同的方法并尝试重新创建密码或重新初始化它以用于不同的目的,但是在所有这些和类似的尝试中,我收到Javax.Crypto.IllegalBlockSizeException
消息“密钥用户未通过身份验证”
密钥库初始化如下:
// If Secretkey was previously created for that keyName, then grab and return it.
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null) // Keystore must be loaded before it can be accessed
keyStore.getKey(keyName, null)?.let { return it as SecretKey }
// if you reach here, then a new SecretKey must be generated for that keyName
val paramsBuilder = KeyGenParameterSpec.Builder(
keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
paramsBuilder.apply {
setBlockModes(ENCRYPTION_BLOCK_MODE) // KeyProperties.BLOCK_MODE_GCM
setEncryptionPaddings(ENCRYPTION_PADDING) // KeyProperties.ENCRYPTION_PADDING_NONE
setKeySize(KEY_SIZE) // 256
setUserAuthenticationRequired(true) // This is required for BiometricPrompt to work properly
}
val keyGenParams = paramsBuilder.build()
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
keyGenerator.init(keyGenParams)
return keyGenerator.generateKey()
}