Android Keystore?在某些设备上返回null的问题

4

我正在实现应用程序中的指纹认证功能。我遇到的问题是,在某些设备上它不能正常工作,导致我的应用程序崩溃了。然而,在某些设备上它却可以正常工作。问题出在这一行代码上:

val key = keyStore?.getKey(Constants.FINGERPRINT_KEY_NAME, null) as SecretKey

LogCat显示:

 11-02 14:27:42.825 E: FATAL EXCEPTION: main
                  Process: kyivenergo.ua.kyivenegro, PID: 2298
                  java.lang.RuntimeException: Unable to start activity ComponentInfo{kyivenergo.ua.kyivenegro/ua.dtec.appOk.ui.screens.splash.SplashScreenActivity}: kotlin.TypeCastException: null cannot be cast to non-null type javax.crypto.SecretKey
                      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2646)
                      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
                      at android.app.ActivityThread.-wrap12(ActivityThread.java)
                      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
                      at android.os.Handler.dispatchMessage(Handler.java:102)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.app.ActivityThread.main(ActivityThread.java:6077)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
                   Caused by: kotlin.TypeCastException: null cannot be cast to non-null type javax.crypto.SecretKey
                      at ua.dtec.appOk.ui.dialog.FingerprintDialog.initCipher(FingerprintDialog.kt:222)
                      at ua.dtec.appOk.ui.dialog.FingerprintDialog.doCheck(FingerprintDialog.kt:147)
                      at ua.dtec.appOk.ui.dialog.FingerprintDialog.show(FingerprintDialog.kt:65)
                      at ua.dtec.appOk.ui.screens.splash.SplashScreenActivity.showFingerprintDialog(SplashScreenActivity.kt:55)
                      at ua.dtec.appOk.ui.screens.splash.SplashScreenActivity.onStart(SplashScreenActivity.kt:33)
                      at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1248)
                      at android.app.Activity.performStart(Activity.java:6679)
                      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2609)
                      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707) 
                      at android.app.ActivityThread.-wrap12(ActivityThread.java) 
                      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460) 
                      at android.os.Handler.dispatchMessage(Handler.java:102) 
                      at android.os.Looper.loop(Looper.java:154) 
                      at android.app.ActivityThread.main(ActivityThread.java:6077) 
                      at java.lang.reflect.Method.invoke(Native Method) 
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866) 
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756

以下是我的完整代码:

private fun doCheck() {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

        keyguardManager =
                c.getSystemService(KEYGUARD_SERVICE) as KeyguardManager
        fingerprintManager =
                c.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager

        if (!fingerprintManager!!.isHardwareDetected()) {

            Toast.makeText(c, R.string.dialog_fingerprint_no_hardware_toast, Toast.LENGTH_SHORT).show()

        }

        if (ActivityCompat.checkSelfPermission(c, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {

            Toast.makeText(c, R.string.dialog_fingerprint_enable_fingerprint_permission, Toast.LENGTH_SHORT).show()
        }

        if (!fingerprintManager!!.hasEnrolledFingerprints()) {

            Toast.makeText(c, R.string.dialog_fingerprint_no_fingerprints, Toast.LENGTH_SHORT).show()
        }

        if (!keyguardManager?.isKeyguardSecure()!!) {
            Toast.makeText(c, R.string.dialog_fingerprint_no_lock_screen, Toast.LENGTH_SHORT).show()
        } else {

            generateKey()

        }

        if (initCipher()) {
            cryptoObject = FingerprintManager.CryptoObject(cipher)

            helper = FingerPrintHelper(dialog!!)
            helper?.FingerprintHandler(c)
            helper?.startAuth(fingerprintManager!!, cryptoObject!!)
        }

    }
}

private fun generateKey() {

    try {

        keyStore = KeyStore.getInstance("AndroidKeyStore")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

            keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
            keyStore?.load(null)
            keyGenerator!!.init(

                    KeyGenParameterSpec.Builder(Constants.FINGERPRINT_KEY_NAME,
                            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
                            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                            .setUserAuthenticationRequired(true)
                            .setEncryptionPaddings(
                                    KeyProperties.ENCRYPTION_PADDING_PKCS7)
                            .build())
        }

        keyGenerator?.generateKey()

    } catch (exc: KeyStoreException) {
        exc.printStackTrace()
        throw FingerprintException(exc)
    } catch (exc: NoSuchAlgorithmException) {
        exc.printStackTrace()
        throw FingerprintException(exc)
    } catch (exc: NoSuchProviderException) {
        exc.printStackTrace()
        throw FingerprintException(exc)
    } catch (exc: InvalidAlgorithmParameterException) {
        exc.printStackTrace()
        throw FingerprintException(exc)
    } catch (exc: CertificateException) {
        exc.printStackTrace()
        throw FingerprintException(exc)
    } catch (exc: IOException) {
        exc.printStackTrace()
        throw FingerprintException(exc)
    }

}

@RequiresApi(Build.VERSION_CODES.M)
fun initCipher(): Boolean {
    try {

        cipher = Cipher.getInstance(
                KeyProperties.KEY_ALGORITHM_AES + "/"
                        + KeyProperties.BLOCK_MODE_CBC + "/"
                        + KeyProperties.ENCRYPTION_PADDING_PKCS7)
    } catch (e: NoSuchAlgorithmException) {
        throw RuntimeException("Failed to get Cipher", e)
    } catch (e: NoSuchPaddingException) {
        throw RuntimeException("Failed to get Cipher", e)
    }

    try {
        keyStore?.load(
                null)
        val key = keyStore?.getKey(Constants.FINGERPRINT_KEY_NAME, null) as SecretKey

        cipher?.init(Cipher.ENCRYPT_MODE, key)
        return true
    } catch (e: KeyPermanentlyInvalidatedException) {

        return false
    } catch (e: KeyStoreException) {
        throw RuntimeException("Failed to init Cipher", e)
    } catch (e: CertificateException) {
        throw RuntimeException("Failed to init Cipher", e)
    } catch (e: UnrecoverableKeyException) {
        throw RuntimeException("Failed to init Cipher", e)
    } catch (e: IOException) {
        throw RuntimeException("Failed to init Cipher", e)
    } catch (e: NoSuchAlgorithmException) {
        throw RuntimeException("Failed to init Cipher", e)
    } catch (e: InvalidKeyException) {
        throw RuntimeException("Failed to init Cipher", e)
    }

}

堆栈跟踪本身比错误消息更重要,特别是由另一个错误引起的情况下。因此,请您能否添加它? - tynn
@tynn 我编辑了我的问题。 - menefrego
你确定generateKey()被调用时没有任何错误吗? - BakaWaii
上面的代码中有太多的!! :( - alla
2个回答

2
您正在将 null 强制转换为不可为空的 SecretKey。
val key = keyStore?.getKey(Constants.FINGERPRINT_KEY_NAME, null) as SecretKey

keyStore为null或getKey()返回null(如果给定的别名不存在或不标识与密钥相关的条目),您可能会得到null。

因此,获取null是完全可以的。由于您正在向上转换,甚至可能遇到ClassCastException。为了防止这种情况,只需使用类型安全的转换运算符as?

val key: SecretKey? = keyStore?.getKey(Constants.FINGERPRINT_KEY_NAME, null) as? SecretKey

当我运行 getKey() 时,在许多华为设备上会得到 null,但是 as? 并不能解决问题,因为使用 null-SecretKey 调用 Cipher 方法会导致进一步的崩溃。 - alla
@所有人,如果键值为空,则无法使用它并且必须以不同的方式处理该情况。这个问题和答案是关于使用 as? 运算符进行安全转换。我建议你提出另一个关于你特定问题的问题。 - tynn
实际上这并不具体,这将是一个重复的问题,因此与当前问题相同,具有相同的描述和相同的异常,但我会尝试。 - alla
@alla 你仍然必须使用as?。然而,_更进一步的崩溃_是另一个问题,你需要用不同的方式解决。 - tynn

0

构建版本应大于或等于棉花糖。 如果您尝试低于M,则可能会引发错误..


我知道这个并已经考虑到了。 - menefrego

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接