这应该可以解决问题:
biometricPrompt.authenticate(null, new CancellationSignal(), executor, callback);
它或多或少地写在错误消息中(可以说hiddensetDeviceCredentialAllowed(true)
):使用时不要使用加密对象。
这一切都分解为如何配置内部加密操作的私钥CryptoObject
。
我假设您用于初始化签名对象的私钥是用setUserAuthenticationRequired(true)
. 使用该选项构建的密钥仅用于一个加密操作。此外,它们必须使用生物识别技术解锁,使用BiometricPrompt.authenticate
或FingerprintManager.authenticate
(现在不推荐使用 BiometricPrompt)。
如果仅在用户经过身份验证后才授权使用密钥,则官方文档讨论了两种模式,即:
setUserAuthenticationRequired(true)
必须使用FingerprintManager.authenticate
(now BiometricPrompt.authenticate
)解锁构建的密钥
- 构建的密钥
setUserAuthenticationValidityDurationSeconds
必须随KeyguardManager.createConfirmDeviceCredentialIntent
流程解锁
官方生物识别身份验证培训指南末尾的KeyguardManager.createConfirmDeviceCredentialIntent
注释建议BiometricPrompt
使用setDeviceCredentialAllowed(true)
.
但这并不像将密钥的 UserAuthenticationValidityDuration 设置为非零值那么简单,因为这将在初始化签名对象后立即触发UserNotAuthenticatedException
内部调用。initSignature(privateKey)
还有更多的警告......见下面的两个例子
生物特征密钥认证
fun biometric_auth() {
val myKeyStore = KeyStore.getInstance("AndroidKeyStore")
myKeyStore.load(null)
val keyGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
"AndroidKeyStore"
)
// build MY_BIOMETRIC_KEY
val keyAlias = "MY_BIOMETRIC_KEY"
val keyProperties = KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
val builder = KeyGenParameterSpec.Builder(keyAlias, keyProperties)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setUserAuthenticationRequired(true)
keyGenerator.run {
initialize(builder.build())
generateKeyPair()
}
val biometricKeyEntry: KeyStore.Entry = myKeyStore.getEntry(keyAlias, null)
if (biometricKeyEntry !is KeyStore.PrivateKeyEntry) {
return
}
// create signature object
val signature = Signature.getInstance("SHA256withECDSA")
// init signature else "IllegalStateException: Crypto primitive not initialized" is thrown
signature.initSign(biometricKeyEntry.privateKey)
val cryptoObject = BiometricPrompt.CryptoObject(signature)
// create biometric prompt
// NOTE: using androidx.biometric.BiometricPrompt here
val prompt = BiometricPrompt(
this,
AsyncTask.THREAD_POOL_EXECUTOR,
object : BiometricPrompt.AuthenticationCallback() {
// override the required methods...
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Log.w(TAG, "onAuthenticationError $errorCode $errString")
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Log.d(TAG, "onAuthenticationSucceeded" + result.cryptoObject)
val sigBytes = signature.run {
update("hello world".toByteArray())
sign()
}
Log.d(TAG, "sigStr " + Base64.encodeToString(sigBytes, 0))
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Log.w(TAG, "onAuthenticationFailed")
}
})
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Unlock your device")
.setSubtitle("Please authenticate to ...")
// negative button option required for biometric auth
.setNegativeButtonText("Cancel")
.build()
prompt.authenticate(promptInfo, cryptoObject)
}
PIN/密码/模式验证
fun password_auth() {
val myKeyStore = KeyStore.getInstance("AndroidKeyStore")
myKeyStore.load(null)
val keyGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
"AndroidKeyStore"
)
// build MY_PIN_PASSWORD_PATTERN_KEY
val keyAlias = "MY_PIN_PASSWORD_PATTERN_KEY"
val keyProperties = KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
val builder = KeyGenParameterSpec.Builder(keyAlias, keyProperties)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
// this would trigger an UserNotAuthenticatedException: User not authenticated when using the fingerprint
// .setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(10)
keyGenerator.run {
initialize(builder.build())
generateKeyPair()
}
val keyEntry: KeyStore.Entry = myKeyStore.getEntry(keyAlias, null)
if (keyEntry !is KeyStore.PrivateKeyEntry) {
return
}
// create signature object
val signature = Signature.getInstance("SHA256withECDSA")
// this would fail with UserNotAuthenticatedException: User not authenticated
// signature.initSign(keyEntry.privateKey)
// create biometric prompt
// NOTE: using androidx.biometric.BiometricPrompt here
val prompt = BiometricPrompt(
this,
AsyncTask.THREAD_POOL_EXECUTOR,
object : BiometricPrompt.AuthenticationCallback() {
// override the required methods...
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Log.w(TAG, "onAuthenticationError $errorCode $errString")
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Log.d(TAG, "onAuthenticationSucceeded " + result.cryptoObject)
// now it's safe to init the signature using the password key
signature.initSign(keyEntry.privateKey)
val sigBytes = signature.run {
update("hello password/pin/pattern".toByteArray())
sign()
}
Log.d(TAG, "sigStr " + Base64.encodeToString(sigBytes, 0))
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Log.w(TAG, "onAuthenticationFailed")
}
})
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Unlock your device")
.setDeviceCredentialAllowed(true)
.build()
prompt.authenticate(promptInfo)
}