Android 生物识别授权密钥永久失效异常

mis*_*ios 2 java encryption android kotlin android-keystore

我正在尝试在 Android 应用程序中实现生物识别授权。我遵循了 android 官方文档,直到今天一切都很好,所以当我删除指纹并添加新指纹时,现在它抛出异常,我尝试将 try catch 放入 getOrCreateSecretKey 但它是相同的:(

\n

android.security.keystore.KeyPermanentlyInvalidatedException:密钥永久失效

\n
private class CryptographyManagerImpl : CryptographyManager {\n\n    private val KEY_SIZE = 256\n    private val ANDROID_KEYSTORE = "AndroidKeyStore"\n    private val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM\n    private val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE\n    private val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES\n\n    override fun getInitializedCipherForEncryption(keyName: String): Cipher {\n        val cipher = getCipher()\n        val secretKey = getOrCreateSecretKey(keyName)\n        cipher.init(Cipher.ENCRYPT_MODE, secretKey)\n        return cipher\n    }\n\n    override fun getInitializedCipherForDecryption(\n        keyName: String,\n        initializationVector: ByteArray\n    ): Cipher {\n        val cipher = getCipher()\n        val secretKey = getOrCreateSecretKey(keyName)\n        cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, initializationVector))\n        return cipher\n    }\n\n    override fun encryptData(plaintext: String, cipher: Cipher): CiphertextWrapper {\n        val ciphertext = cipher.doFinal(plaintext.toByteArray(Charset.forName("UTF-8")))\n        return CiphertextWrapper(ciphertext, cipher.iv)\n    }\n\n    override fun decryptData(ciphertext: ByteArray, cipher: Cipher): String {\n        val plaintext = cipher.doFinal(ciphertext)\n        return String(plaintext, Charset.forName("UTF-8"))\n    }\n\n    private fun getCipher(): Cipher {\n        val transformation = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING"\n        return Cipher.getInstance(transformation)\n    }\n\n    private fun getOrCreateSecretKey(keyName: String): SecretKey {\n        // If Secretkey was previously created for that keyName, then grab and return it.\n        val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)\n        keyStore.load(null) // Keystore must be loaded before it can be accessed\n        keyStore.getKey(keyName, null)?.let { return it as SecretKey }\n\n        // if you reach here, then a new SecretKey must be generated for that keyName\n        val paramsBuilder = KeyGenParameterSpec.Builder(\n            keyName,\n            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT\n        )\n        paramsBuilder.apply {\n            setBlockModes(ENCRYPTION_BLOCK_MODE)\n            setEncryptionPaddings(ENCRYPTION_PADDING)\n            setKeySize(KEY_SIZE)\n            setUserAuthenticationRequired(true)\n        }\n\n        val keyGenParams = paramsBuilder.build()\n        val keyGenerator = KeyGenerator.getInstance(\n            KeyProperties.KEY_ALGORITHM_AES,\n            ANDROID_KEYSTORE\n        )\n        keyGenerator.init(keyGenParams)\n        return keyGenerator.generateKey()\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

例外

\n
2021-10-19 17:28:05.252 6613-6613/com.samplebet E/AndroidRuntime: FATAL EXCEPTION: main\n    Process: com.samplebet, PID: 6613\n    java.lang.RuntimeException: java.lang.reflect.InvocationTargetException\n        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612)\n        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)\n     Caused by: java.lang.reflect.InvocationTargetException\n        at java.lang.reflect.Method.invoke(Native Method)\n        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)\n        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)\xc2\xa0\n     Caused by: android.security.keystore.KeyPermanentlyInvalidatedException: Key permanently invalidated\n        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1533)\n        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1548)\n        at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)\n        at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)\n        at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265)\n        at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:148)\n        at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2980)\n        at javax.crypto.Cipher.tryCombinations(Cipher.java:2891)\n        at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2796)\n        at javax.crypto.Cipher.chooseProvider(Cipher.java:773)\n        at javax.crypto.Cipher.init(Cipher.java:1288)\n        at javax.crypto.Cipher.init(Cipher.java:1223)\n        at com.samplebet.biometric.CryptographyManagerImpl.getInitializedCipherForDecryption(CryptographyManager.kt:98)\n        at com.samplebet.auth.AuthActivity.showBiometricPromptForDecryption(AuthActivity.kt:120)\n        at com.samplebet.auth.AuthActivity.onCreate$lambda-5(AuthActivity.kt:87)\n        at com.samplebet.auth.AuthActivity.$r8$lambda$xxAGKHOwnj6tq1hGqTWaney7IFw(Unknown Source:0)\n        at com.samplebet.auth.AuthActivity$$ExternalSyntheticLambda0.onClick(Unknown Source:2)\n        at android.view.View.performClick(View.java:8160)\n        at android.widget.TextView.performClick(TextView.java:16222)\n        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)\n        at android.view.View.performClickInternal(View.java:8137)\n        at android.view.View.access$3700(View.java:888)\n        at android.view.View$PerformClick.run(View.java:30236)\n        at android.os.Handler.handleCallback(Handler.java:938)\n        at android.os.Handler.dispatchMessage(Handler.java:99)\n        at android.os.Looper.loop(Looper.java:246)\n
Run Code Online (Sandbox Code Playgroud)\n

小智 5

发生这种情况是因为在您的密钥生成器参数中包含setUserAuthenticationRequired(true),当所有生物识别信息被删除或在设备上添加新的生物识别信息时,该设置会使密钥无效。

这意味着,当您在删除指纹并添加另一个指纹后尝试使用此类密钥解密时,您将在调用cipher.init时抛出KeyPermanentlyInvalidatedException

捕获异常、删除无效密钥并生成新密钥是解决方案的一部分,因为使用新密钥,您将无法再解密加密的(使用前一个密钥)数据。为此,您必须有一个后备流程,要求用户使用新密钥再次加密数据。

最后注意事项:

  • 您可以在生成新密钥时使用setInvalidatedByBiometricEnrollment方法(可从 API 24 获取)并将其设置为 false,这将缩小密钥失效的情况,但可能被认为安全性稍差。

  • 不建议在生成密钥时将 setUserAuthenticationRequired 设置为 false(这也可以解决您的问题)。