【问题标题】:java.security.UnrecoverableKeyException: Failed to obtain information about private keyjava.security.UnrecoverableKeyException:获取私钥信息失败
【发布时间】:2016-08-07 18:04:10
【问题描述】:

我有以下几行从 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

【问题讨论】:

  • 很可能您无能为力。以防万一,在执行 KeyStore.getPrivateKey 期间系统日志中是否有任何有趣的行?另外,您遇到的异常的完整堆栈跟踪(包括根本原因)是什么?
  • 另外,您在哪台 Android 设备上看到了这个?
  • 抱歉回复晚了,添加了异常跟踪。根据出厂映像列表developers.google.com/android/nexus/images,我正在使用 Nexus 5 测试行为,这是少数可以从 Android 4 升级到 Android 6 的设备之一
  • 谢谢。在 KeyStore.getPrivateKey 的执行过程中,系统日志中是否有任何有趣的行,而不是堆栈跟踪?
  • 不确定你指的是什么系统日志,但是异常跟踪是通过执行exeption.printStackTrace()在logcat中看到的打印输出。

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


【解决方案1】:

什么时候发生这个错误,为什么?

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

更新:

从您的错误日志中,现在很明显

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

【讨论】:

  • 感谢您向我展示源代码。密钥库状态也是我之前猜到的,但是除了故意引发异常之外,我似乎找不到在获取私钥之前解锁密钥库的方法。
  • 实际上问题在于从 LOCK/UNINITIALIZE 解锁。但是在这种情况下你可能会很棘手。您可以通过使用计数器处理异常来再次调用。我已经用解决方案更新了我的答案。请试一试。
  • 我对这个答案有点困惑。目前尚不清楚尝试 getEntry() 两次如何解决该问题。它与 30 秒超时有什么关系?另外,为什么这是必要的:“您必须从 catch 参数中删除 InvalidKeyException 类”..?为什么不直接捕获特定的 InvalidKeyException 以进行第二次尝试呢?您是否有意吞下所有其他异常而不重新抛出它们?
  • 删除条目然后生成新条目是一种可接受的解决方案吗?如果用户有加密信息需要使用密钥库中的现有私钥进行恢复怎么办?
  • 不明白为什么要调用deleteEntry
【解决方案2】:

更新(2020 年 8 月):

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

在他们提到的长乐日志中:

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

}

【讨论】:

  • 对于那些渴望了解更多信息的人,这是 Tink 库中修复的核心问题 - github.com/google/tink/issues/339。 Android X Security 依赖于这个库,它是在 1.0.0-rc03 中发布的。现在 Android X Security 在实际使用密钥库之前会进行自检。
【解决方案3】:

其实Android keystore 不是线程安全的,即使我们EncryptedSharedPreferences,它也不是线程安全的。 Google 已经在this document 中明确告诉我们,

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

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

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

【讨论】:

猜你喜欢
  • 2018-06-25
  • 2013-11-27
  • 1970-01-01
  • 1970-01-01
  • 2015-11-27
  • 2011-11-07
  • 1970-01-01
  • 1970-01-01
  • 2022-07-27
相关资源
最近更新 更多