30

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

3 回答 3

13

何时发生此错误,为什么?

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 类
  2. 密钥库 java 类

更新:

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

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.

解决方案:

您必须InvalidKeyException从 catch 参数中删除该类。这仍然允许您检查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:无效的密钥 blob
于 2016-04-20T14:52:30.187 回答
4

更新(2020 年 8 月):

将安全库更新到版本1.0.0-rc03为我解决了这个问题。

他们在长乐日志中提到:

Tink 更新应该优雅地处理 AndroidKeyStore 并发故障。


老答案:

issuetracker上有一个未解决的问题

这是一位 Google 工程师的回复

AndroidKeyStore 的一些 OEM 实现已损坏,无法正常工作。不幸的是,Jetpack Security 依赖 AndroidKeyStore 来安全地存储和生成密钥。如果这不起作用,您所能做的就是信任有故障的设备,少信任并且不使用加密。理想情况下,在库中进行检查以找到这些问题会很好,这样您就可以在没有随机崩溃的情况下了解这一点。

我编写了一个测试类,您可以同时使用它来测试 KeyStore。基本上,您必须进行端到端的加密/解密,以了解设备 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;
    }

}
于 2020-07-06T21:17:00.840 回答
2

实际上 Android keystore 不是线程安全的,即使是 we EncryptedSharedPreferences,它也不是线程安全的。谷歌在这份文件中明确告诉我们,

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

Once there are multiple threads trying to access the Android keystore, any exception could happened. Like above exception is happened when one thread is using key, but another thread tries to get the key again.

All Android keystore operations should be put into synchronized block.

于 2020-09-06T05:33:16.053 回答