AndroidKeystore 密钥无法初始化密码

AndroidKeystore secretkey Can't init cipher

我在我的应用程序中使用 AndroidKeyStore 来存储密钥,我正在使用 KeyGenParameterSpec class 生成该密钥。这是代码:

final SecretKey secretKey;
        final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
        final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEY_ALIAS,
            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setUserAuthenticationRequired(true)
            .setUserAuthenticationValidityDurationSeconds(<validityInSeconds>)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .setRandomizedEncryptionRequired(false)
            .build();
        keyGenerator.init(keyGenParameterSpec);
        secretKey = keyGenerator.generateKey();

之后,我尝试用这个密钥初始化密码:

final Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
                + KeyProperties.ENCRYPTION_PADDING_PKCS7);

            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

现在,AndroidKeyStore 似乎有一个奇怪的问题。 cipher.init 在某些情况下会失败,奇怪的是,除了为 validityInSecs 尝试不同的值外,我保留了所有内容 same/constant。当它失败时,它会失败并出现此错误:

Caused by: java.lang.NullPointerException: Attempt to get length of null array
        at org.spongycastle.crypto.params.KeyParameter.<init>(KeyParameter.java:13)
        at org.spongycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(BaseBlockCipher.java:615)
        at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2659)
        at javax.crypto.Cipher.tryCombinations(Cipher.java:2570)
        at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
        at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
        at javax.crypto.Cipher.init(Cipher.java:973)
        at javax.crypto.Cipher.init(Cipher.java:908)

我已经为 validityInSec 尝试了不同的值,它在某些情况下失败(对于非常低的值)并在某些情况下成功(当我增加值时)。以下是一些数据点:

10s -> fail
1800s -> success
100s -> fail
750s -> success

另一个有趣的事情是,对于相同的值,它有时会失败,有时会成功。这些值的示例是 750s625s.

我不知道这里发生了什么。

Device: One plus 3t
OS: 8.0.0

请帮忙。

编辑:

这里的堆栈跟踪有点误导,因为它有来自 Spongycastle 库的跟踪。

这是 android 框架 class 中的错误。对于密码,它会逐一尝试所有不同的提供者,如果失败,则会将异常存储在单个变量中。问题是,它不会更新那个异常变量。因此,如果所有提供者的密码都失败,则它抛出的异常来自第一个提供者。因此,它为您提供了仅尝试过该提供程序的表达式。

相关代码如下: http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/javax/crypto/Cipher.java#2546

就我而言,在对成功案例进行 class-by-class 调试后,我发现除了 SC,它也在尝试其他提供商。相关截图:

更新 2:

我试过了final Cipher cipher = Cipher.getInstance(<transformation>,"AndroidKeyStoreBCWorkaround");

现在,当我将 duration 作为 1800s 传递时,它成功了。当我通过 40s 时,它失败并出现以下错误:

06-28 20:06:55.462 624-624/in.zeta.android E/EncryptedStoreService: android.security.keystore.UserNotAuthenticatedException: User not authenticated
        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:741)
        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:777)
        at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
        at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
        at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265)
        at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:109)
        at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2663)
        at javax.crypto.Cipher.tryCombinations(Cipher.java:2556)
        at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
        at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
        at javax.crypto.Cipher.init(Cipher.java:830)
        at javax.crypto.Cipher.init(Cipher.java:771)

我不知道 validityInSeconds 会如何影响代码,但异常与此无关

Caused by: java.lang.NullPointerException: Attempt to get length of null array
    at org.spongycastle.crypto.params.KeyParameter.<init>(KeyParameter.java:13)

异常是因为密钥由 AndroidKeyStore 管理,但 Cipher 使用加密提供程序 spongycastle 。 AES 密钥不可提取,spongycastle 需要它才能加密

从您的项目中删除 spongycastle 或在您初始化 Cipher

时强制使用 AndroidKeyStore
 Cipher cipher = Cipher.getInstance(algorithm, "AndroidKeyStore");

加密提供程序 select 按照在操作系统中的安装顺序排列。如果未明确指定加密提供程序,Android 将根据上述内容 select spongycastle 或 AndroidKeyStore。我不知道 validityInSeconds 是如何影响的,但可能有一些内部 Android 行为改变了顺序

我试过了

final Cipher cipher = Cipher.getInstance(<transformation>,"AndroidKeyStoreBCWorkaround");

通过设置断点,我发现这是实际工作的提供程序。

现在,当我将 duration 作为 1800s 传递时,它成功了。当我通过 40s 时,它因以下错误而失败:

06-28 20:06:55.462 624-624/in.zeta.android E/EncryptedStoreService: android.security.keystore.UserNotAuthenticatedException: User not authenticated
        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:741)
        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:777)
        at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
        at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
        at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265)
        at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:109)
        at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2663)
        at javax.crypto.Cipher.tryCombinations(Cipher.java:2556)
        at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
        at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
        at javax.crypto.Cipher.init(Cipher.java:830)
        at javax.crypto.Cipher.init(Cipher.java:771)

来自堆栈跟踪的相关源代码 links:

https://android.googlesource.com/platform/frameworks/base/+/508e665/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java#215

https://android.googlesource.com/platform/frameworks/base/+/83a86c5/keystore/java/android/security/KeyStoreCryptoOperationUtils.java#42

https://android.googlesource.com/platform/frameworks/base/+/master/keystore/java/android/security/KeyStore.java#707

在第二个 link 之后,它不会进入 KeyStore.OP_AUTH_NEEDED,否则它会从那里返回 null。大多数情况下,它处于 LOCKED 状态。 (https://android.googlesource.com/platform/frameworks/base/+/master/keystore/java/android/security/KeyStore.java#58)

我知道它在初始化密码时是否需要用户身份验证,但很好奇为什么它在 1800s 的持续时间内是成功的。我让我的应用程序打开了 > 1800s 然后尝试了这个持续时间,它失败了同样的异常。然后我通过了 18000s 并且成功了。因此,事实证明它会检查用户是否在过去的 keyvalidity 秒内通过了身份验证,这完全有道理,很明显,如果我没记错的话,也会出现在文档中。

所以整个混乱是由于 Cipher 抛出的错误异常 class >.<

我将在尝试初始化密码之前尝试对用户进行身份验证,看看它是否有效