java.security.UnrecoverableKeyException: не удалось получить информацию о закрытом ключе

У меня есть следующие строки, чтобы получить закрытый ключ из хранилища ключей на 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

1 ответ

Решение

Когда происходит эта ошибка и почему?

Ответ: При загрузке ключей Android и сохранении открытого ключа из хранилища ключей эта ошибка может произойти, если состояние заблокировано или неинициализировано.

Ошибка генерации кода части приведена ниже:

@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 происходит только тогда, когда состояние разблокировано. Для вашего случая обновления состояние 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. KeyStore 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, После проверки вы должны повторить попытку с кодом, чтобы проблема не отображалась на глаз, но повторная проверка может решить вашу проблему. Я не проверял код, но должен быть как ниже:

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: недействительный ключевой объект

ОБНОВЛЕНИЕ (август 2020 г.):

Обновление библиотеки безопасности до версии1.0.0-rc03 устраняет проблему для меня.

В журнале изменений они упоминают:

Обновление Tink должно корректно обрабатывать сбои параллелизма AndroidKeyStore.


Старый ответ:

Существует открытый вопрос о issuetracker для этого

Вот ответ одного из инженеров Google

Некоторые OEM-реализации AndroidKeyStore сломаны и не работают должным образом. К сожалению, 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;
    }

}

На самом деле хранилище ключей Android не является потокобезопасным, даже если мы EncryptedSharedPreferences, он также не является потокобезопасным. Google ясно сказал нам в этом документе,

Примечание. Методы как в классе EncryptedFile, так и в классе EncryptedSharedPreferences не являются потокобезопасными.

Если несколько потоков пытаются получить доступ к хранилищу ключей Android, может произойти любое исключение. Как и выше, исключение возникает, когда один поток использует ключ, но другой поток пытается снова получить ключ.

Все операции с хранилищем ключей Android должны быть помещены в synchronized блок.

Другие вопросы по тегам