我有以下几行来从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
答案 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);
}
}
资源链接:
从您的错误日志中,现在很清楚
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 :(得分: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)
块中。