我尝试使用绑定身份验证的解密密钥来实现BiometricPrompt,但没有成功(不允许使用pin/密码/模式替代方案(。我使用非对称密钥,因为我需要在没有用户身份验证的情况下加密字符串,并在需要用户身份验证时解密字符串。然而,当我尝试使用BiometricPrompt为onAuthenticationSuccessed回调提供的cryptoObject时,我得到了IllegalBlockSizeException nullcode=100104。如果我只是简单地将setUserAuthenticationRequired设置为false,那么一切都会正常工作,无一例外。如果身份验证有任何问题,我不会得到UserNotAuthenticatedException吗?如果加密有任何问题,无论设置UserAuthenticationRequired,我都不会得到IllegalBlockSizeException。这个非法BlockSizeException的来源是什么?我该怎么解决?
编码:
fun encode(
keyAlias: String,
decodedString: String,
isAuthorizationRequired: Boolean
): String {
val cipher: Cipher = getEncodeCipher(keyAlias, isAuthorizationRequired)
val bytes: ByteArray = cipher.doFinal(decodedString.toByteArray())
return Base64.encodeToString(bytes, Base64.NO_WRAP)
}
//allow encoding without user authentication
private fun getEncodeCipher(
keyAlias: String,
isAuthenticationRequired: Boolean
): Cipher {
val cipher: Cipher = getCipherInstance()
val keyStore: KeyStore = loadKeyStore()
if (!keyStore.containsAlias(keyAlias))
generateKey(keyAlias, isAuthenticationRequired
)
//from https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html
val key: PublicKey = keyStore.getCertificate(keyAlias).publicKey
val unrestricted: PublicKey = KeyFactory.getInstance(key.algorithm).generatePublic(
X509EncodedKeySpec(key.encoded)
)
val spec = OAEPParameterSpec(
"SHA-256", "MGF1",
MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT
)
cipher.init(Cipher.ENCRYPT_MODE, unrestricted, spec)
return cipher
}
密钥生成:
private fun generateKey(keyAlias: String, isAuthenticationRequired: Boolean) {
val keyGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore"
)
keyGenerator.initialize(
KeyGenParameterSpec.Builder(
keyAlias,
KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT
).run {
setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
setUserAuthenticationRequired(isAuthenticationRequired) //only if isAuthenticationRequired is false -> No IllegalBlockSizeException during decryption
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setInvalidatedByBiometricEnrollment(true)
}
build()
}
)
keyGenerator.generateKeyPair()
}
}
解码:
//this Cipher is passed to the BiometricPrompt
override fun getDecodeCipher(keyAlias: String): Cipher {
val keyStore: KeyStore = loadKeyStore()
val key: PrivateKey = keyStore.getKey(keyAlias, null) as PrivateKey
cipher.init(Cipher.DECRYPT_MODE, key)
return cipher
}
//this is called from inside onAuthenticationSucceeded BiometricPrompt.AuthenticationCallback
fun decodeWithDecoder(encodedStrings: List<String>, cryptoObject: BiometricPrompt.CryptoObject): List<String> {
return try {
encodedStrings.map {
val bytes: ByteArray = Base64.decode(it, Base64.NO_WRAP)
//here i get IllegalBlockSizeException after the first iteration if isAuthenticationRequired is set to true
String(cryptoObject.cipher!!.doFinal(bytes))
}
}
生物识别提示:
private fun setUpBiometricPrompt() {
executor = ContextCompat.getMainExecutor(requireContext())
val callback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Log.d("${this.javaClass.canonicalName}", "onAuthenticationError $errString")
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Log.d("${this.javaClass.canonicalName}", "Authentication failed")
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
//passing the received crypto object on for decryption operation (which then fails)
decodeWithDecoder(encodedString: String, result.cryptoObject)
super.onAuthenticationSucceeded(result)
}
}
promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Biometric login for my app")
.setSubtitle("Log in using your biometric credential")
.setNegativeButtonText("Use account password")
.build()
biometricPrompt = BiometricPrompt(this, executor, callback)
}
//show the prompt
fun authenticate() {
biometricPrompt.authenticate(
promptInfo,
BiometricPrompt.CryptoObject(getDecodeCipher()))
}
我意识到我最初的代码示例中缺少了一个相当重要的细节。事实上,我试图使用cryptoObec中的密码进行多次加密操作。我在上面的样本中添加了这个细节,因为这显然是异常的原因。因此,答案是,很明显,在密钥上设置setUserAuthenticationRequired的方式会影响一次使用(一次性(初始化的密码对象的频率。如果设置为false,则可以多次使用is,如果设置为true,则只能使用一次。还是我在这里错过了什么?当然,问题仍然存在,我如何用用户身份验证绑定的密钥解密多个字符串?其他人也有类似的问题Android-使用指纹扫描仪和密码加密和解密多个字符串