java.security.UnrecoverableKeyException:无法获取有关私钥的信息

时间:2016-04-15 16:46:02

标签: java android keystore private-key java-security

我有以下几行来从Android上的密钥库获取私钥

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

// generating key pair code omitted

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

一切正常,但当操作系统从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

3 个答案:

答案 0 :(得分:6)

发生此错误时为什么?

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

生成部分代码的错误如下:

@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有3个州。它们被解锁,锁定,无法使用

     

NO_ERROR仅在状态为UNLOCKED时发生。为您   升级案例状态第一次是LOCKED或UNINITIALIZED,所以   错误只发生一次。

状态检查代码如下:

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);
    }
}

资源链接:

  1. AndroidKeyStoreProvider java class
  2. KeyStore java class
  3. UPDATE:

    从您的错误日志中,现在很清楚

    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;
    

    对于加密/解密,只有用户刚通过设备凭据进行身份验证时,带有生成密钥的某些数据才有效。错误发生在

    // 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类。这仍然允许您检查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;
            }
        }
    }
    

    资源链接:

    1. MainActivity.java
    2. android.security.KeyStoreException: Invalid key blob

答案 1 :(得分:1)

更新(2020年8月):

security library更新为版本1.0.0-rc03可以解决此问题。

他们在changle日志中提到:

Tink更新应正常处理AndroidKeyStore并发 失败。


旧答案:

issuetracker上有一个未解决的问题

以下是一位Google工程师的回复

AndroidKeyStore的某些OEM实现被破坏,并且没有 好好工作。不幸的是,Jetpack Security依赖于 AndroidKeyStore安全地存储和生成密钥。如果不是这样 工作,您所能做的就是相信有故障的设备,更少 而不使用加密。理想情况下,在图书馆检查会很好 找到这些问题,以便您可以随意了解 崩溃。

我编写了一个测试类,您可以在此期间测试 密钥库。基本上,您必须端到端进行加密/解密 知道设备的KeyStore是否可以正常工作。

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;
    }

}

答案 2 :(得分:1)

实际上,Android密钥库也不是线程安全的,即使我们"Uncaught TypeError: window.getDeviceDetails is not a function" ,它也不是线程安全的。 Google在this document中明确告诉了我们

注意:EncryptedFile类和EncryptedSharedPreferences类中的方法都不是线程安全的。

一旦有多个线程尝试访问Android密钥库,则可能发生任何异常。就像上面的异常一样,当一个线程使用密钥但另一个线程尝试再次获取密钥时发生。

所有Android密钥库操作都应放在 "wrong 0th argtype", source: https://checkout.razorpay.com/v1/checkout-frame.js (1) 块中。