java.security.UnrecoverableKeyException: 获取私钥信息失败

java.security.UnrecoverableKeyException: Failed to obtain information about private key

我有以下几行从 Android

上的密钥库中获取私钥
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

// generating key pair code omitted

KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) this.keyStore.getEntry("alias", null);

一切正常,除了当 OS 从 Android 5.1.1 升级到 Android 6.0.1 时,第 3 行将首先抛出 java.security.UnrecoverableKeyException: Failed to obtain information about private key执行。但之后它会再次正常工作。现在我的解决方法是执行该行 2 次。同时,我也在想有没有更好的方法来避免异常。

更新

异常跟踪

W/System.err﹕ java.security.UnrecoverableKeyException: Failed to obtain information about private key
W/System.err﹕ at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStorePublicKeyFromKeystore(AndroidKeyStoreProvider.java:217)
W/System.err﹕ at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(AndroidKeyStoreProvider.java:253)
W/System.err﹕ at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(AndroidKeyStoreProvider.java:263)
W/System.err﹕ at android.security.keystore.AndroidKeyStoreSpi.engineGetKey(AndroidKeyStoreSpi.java:93)
W/System.err﹕ at java.security.KeyStoreSpi.engineGetEntry(KeyStoreSpi.java:372)
W/System.err﹕ at java.security.KeyStore.getEntry(KeyStore.java:645)
W/System.err﹕ at com.example.keystoretest.MainActivity.onCreate(MainActivity.java:113)
W/System.err﹕ at android.app.Activity.performCreate(Activity.java:6251)
W/System.err﹕ at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
W/System.err﹕ at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
W/System.err﹕ at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
W/System.err﹕ at android.app.ActivityThread.-wrap11(ActivityThread.java)
W/System.err﹕ at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
W/System.err﹕ at android.os.Handler.dispatchMessage(Handler.java:102)
W/System.err﹕ at android.os.Looper.loop(Looper.java:148)
W/System.err﹕ at android.app.ActivityThread.main(ActivityThread.java:5417)
W/System.err﹕ at java.lang.reflect.Method.invoke(Native Method)
W/System.err﹕ at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
W/System.err﹕ at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
W/System.err﹕ Caused by: android.security.KeyStoreException: Invalid key blob
W/System.err﹕ at android.security.KeyStore.getKeyStoreException(KeyStore.java:632)
W/System.err﹕ at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStorePublicKeyFromKeystore(AndroidKeyStoreProvider.java:218)
W/System.err﹕ ... 18 more

此错误发生的时间和原因?

Ans:加载Android密钥并从Keystore存储public密钥时,如果状态被锁定或未初始化,可能会出现此错误。

错误生成部分代码如下:

@NonNull
    public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
            @NonNull KeyStore keyStore, @NonNull String privateKeyAlias)
            throws UnrecoverableKeyException {
        KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
        int errorCode = keyStore.getKeyCharacteristics(privateKeyAlias, null,
                null, keyCharacteristics);
        if (errorCode != KeyStore.NO_ERROR) {
            throw (UnrecoverableKeyException) new UnrecoverableKeyException(
                    "Failed to obtain information about private key")
                    .initCause(KeyStore.getKeyStoreException(errorCode)); // this exception is generated
        }
        ......
        ......
        ......
    }

KeyStore 有 10 个响应代码。他们是

// ResponseCodes
NO_ERROR = 1;
LOCKED = 2;
UNINITIALIZED = 3;
SYSTEM_ERROR = 4;
PROTOCOL_ERROR = 5;
PERMISSION_DENIED = 6;
KEY_NOT_FOUND = 7;
VALUE_CORRUPTED = 8;
UNDEFINED_ACTION = 9;
WRONG_PASSWORD = 10;

KeyStore has 3 states. They are UNLOCKED, LOCKED, UNINITIALIZED

NO_ERROR is only happened when the state is UNLOCKED. For your upgrading case the state is LOCKED or UNINITIALIZED for first time, so the error is happened only once.

状态检查代码如下:

public State state() {
    execute('t');
    switch (mError) {
    case NO_ERROR:
        return State.UNLOCKED;
    case LOCKED:
        return State.LOCKED;
    case UNINITIALIZED:
        return State.UNINITIALIZED;
    default:
        throw new AssertionError(mError);
    }
}

资源Link:

  1. AndroidKeyStoreProvider java class
  2. KeyStore java class

更新:

从您的错误日志中可以清楚地看出

W/System.err﹕ Caused by: android.security.KeyStoreException: Invalid key blob

这是用户尝试从 LOCK/UNINITIALIZED 解锁时导致的主要问题。它默认定义为 30 秒的计时。 此问题是 API 相关的实施问题。

/**
 * If the user has unlocked the device Within the last this number of seconds,
 * it can be considered as an authenticator.
 */
private static final int AUTHENTICATION_DURATION_SECONDS = 30;

对于 encryption/decryption 某些具有生成密钥的数据仅在用户刚刚通过设备凭据进行身份验证时才有效。错误发生于

// Try encrypting something, it will only work if the user authenticated within
// the last AUTHENTICATION_DURATION_SECONDS seconds.
cipher.init(Cipher.ENCRYPT_MODE, secretKey); // error is generated from here.

真正的错误是从这里抛出的。您的错误是从 InvalidKeyException.

生成的

解决方案:

您必须从 catch 参数中删除 InvalidKeyException class。这仍然允许您检查 InvalidKeyException。检查后,您必须再次尝试使用代码,这样问题就不会出现在眼睛中,但进行 2 次检查可能会解决您的问题。我没有测试代码,但应该如下所示:

try {
....
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) this.keyStore.getEntry("alias", null);
....
} catch (final Exception e) {
    e.printStackTrace();
    if (e instanceof InvalidKeyException) { // bypass InvalidKeyException
        .......
        // You can again call the method and make a counter for deadlock situation or implement your own code according to your situation
        if (retry) {
            keyStore.deleteEntry(keyName);
            return getCypher(keyName, false);
        } else {
            throw e;
        }
    }
}

资源Link:

  1. MainActivity.java

更新(2020 年 8 月):

security library 更新到版本 1.0.0-rc03 解决了我的问题。

他们在 changle 日志中提到:

Tink update should gracefully handle AndroidKeyStore concurrency failures.


旧答案:

issuetracker 上有一个未解决的问题

这是 Google 工程师

之一的回复

Some OEM implementations of the AndroidKeyStore are broken and do not work properly. Unfortunately, Jetpack Security relies on the AndroidKeyStore to securely store and generate keys. If this is not working, all you can do is trust the devices that have failures, less and not use encryption. Ideally a check would be nice in the library to find these issues so you can know about this without random crashes.

I wrote a test class you could use in the meantime to test the KeyStore. Basically, you have to do an end to end, encrypt/decrypt to know if the device KeyStore is fully working.

https://gist.github.com/jmarkoff/44f5a9cab1a881c8b0abc787791add08

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//packgage com.company.app

import android.content.Context;
import android.content.SharedPreferences;
import android.security.keystore.KeyGenParameterSpec;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.security.crypto.MasterKeys;
import androidx.security.crypto.EncryptedSharedPreferences;

import java.io.IOException;
import java.security.GeneralSecurityException;

/**
 * Convenient method to test the Android Keystore before using encryption/decryption. A small number
 * OEMs have devices with a bad keystore and KeyStore exceptions will occur.
 *
 * Requires Jetpack Security - https://developer.android.com/jetpack/androidx/releases/security
 *
 * Bugs:
 *
 * https://issuetracker.google.com/issues/147480931
 * https://issuetracker.google.com/issues/134417365
 * https://issuetracker.google.com/issues/150221071
 *
 */
public final class TestKeyStore {

     /**
     * Test the keystore, encryption and decryption on the device. This is useful to find devices
     * that have a bad keystore and encryption should not be used. It is up to the developer to
     * decide how to handle when a bad keystore is encountered. We recommend that the device be
     * trusted less by your app if possible.
     *
     * @param keyGenParameterSpec The key encryption scheme
     * @return true if the keystore can be relied on, false otherwise
     */
    public static boolean trustDeviceKeyStore(@NonNull KeyGenParameterSpec keyGenParameterSpec,
            @NonNull Context context) {
        try {
            String keyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);
            SharedPreferences sharedPreferences =
                    EncryptedSharedPreferences.create("test_keystore", keyAlias,
                            context,
                            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
            SharedPreferences.Editor editor = sharedPreferences.edit();
            editor.putString("TestKeyStore", "Testing");
            editor.commit();
            String value = sharedPreferences.getString("TestKeyStore", "Failed");
            if (value.equals("Testing")) {
                return true;
            }
        } catch (GeneralSecurityException ex) {
            Log.e(TestKeyStore.class.getSimpleName(),
                    "SecurityException: Could be a keystore issue, check the error for more "
                            + "details message: " + ex.getMessage() + ".\n Stacktrace:\n"
                            + ex.getStackTrace().toString());
        } catch (IOException ex) {
            Log.e(TestKeyStore.class.getSimpleName(),
                    "IOException: Check to make sure you have enough disk space and that the "
                            + "file doesn't exist." + ex.getMessage());
        }
        return false;
    }

}

其实Androidkeystore不是thread-safe,即使我们EncryptedSharedPreferences,也不是thread-safe。 Google在this document,

中已经明确告诉我们

Note: The methods in both the EncryptedFile class and the EncryptedSharedPreferences class aren't thread-safe.

一旦有多个线程试图访问 Android 密钥库,任何异常都可能发生。当一个线程正在使用密钥时,会发生上述异常,但另一个线程会再次尝试获取密钥。

所有 Android 密钥库操作都应放入 synchronized 块中。