最近我的个人应用KeepassA在bugly上收到了一个行奇怪的问题。

错误日志:

java.lang.RuntimeException:java.lang.reflect.InvocationTargetException
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:549)
......
Caused by:
android.security.keystore.KeyPermanentlyInvalidatedException:Key permanently invalidated
android.security.KeyStore.getInvalidKeyException(KeyStore.java:1368)
android.security.KeyStore.getInvalidKeyException(KeyStore.java:1413)

android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265)
android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:109)

一、场景

使用指纹在加密了用户信息,并将加密信息保存到keystore中。

当用户登录时,验证指纹成功后,读取keystore中的加密信息,然后解密。

在androidQ以下的版本上倒没什么问题,在androidQ上却出现了该问题,并且在小米的Room中出现的概率特别大。

二、原因

这次的异常比较查找起来比较简单,观察日志,查看抛出异常的地方getInvalidKeyException

public InvalidKeyException getInvalidKeyException(
String keystoreKeyAlias, int uid, KeyStoreException e) {
switch (e.getErrorCode()) {

case OP_AUTH_NEEDED:
{
....
// We now need to determine whether the key/operation can become usable if user
// authentication is performed, or whether it can never become usable again.
// User authentication requirements are contained in the key's characteristics. We
// need to check whether these requirements can be be satisfied by asking the user
// to authenticate.
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
int getKeyCharacteristicsErrorCode =
getKeyCharacteristics(keystoreKeyAlias, null, null, uid,
keyCharacteristics);
if (getKeyCharacteristicsErrorCode != NO_ERROR) {
return new InvalidKeyException(
"Failed to obtained key characteristics",
getKeyStoreException(getKeyCharacteristicsErrorCode));
}
List<BigInteger> keySids =
keyCharacteristics.getUnsignedLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID);
if (keySids.isEmpty()) {
// Key is not bound to any SIDs -- no amount of authentication will help here.
return new KeyPermanentlyInvalidatedException();
}

...

// None of the key's SIDs can ever be authenticated
return new KeyPermanentlyInvalidatedException();
}
case UNINITIALIZED:
return new KeyPermanentlyInvalidatedException();
default:
return new InvalidKeyException("Keystore operation failed", e);
}
}

通过这个case,发现,只有OP_AUTH_NEEDEDUNINITIALIZED才会抛出这个异常,查看下这两个case的定义

  • OP_AUTH_NEEDED

    表示在update、finish 之前没有做校验

/**
* Per operation authentication is needed before this operation is valid.
* This is returned from {@link #begin} when begin succeeds but the operation uses
* per-operation authentication and must authenticate before calling {@link #update} or
* {@link #finish}.
*/
public static final int OP_AUTH_NEEDED = 15;
  • UNINITIALIZED

    表示keystore没有初始化

再次观察错误日志,发现是engineInit处出现的异常,因此可以确定走的是UNINITIALIZED,而在代码中并没有做判空的处理,一位着我获取到的SecretKey也许是空的。

private fun getSk(): SecretKey {
var key = keyStore.getKey(keystoreAlias, keyStorePass)
return key as SecretKey
}

三、解决

private fun getSk(): SecretKey {
var key = keyStore.getKey(keystoreAlias, keyStorePass)
if (key == null){
key = generateKey()
}
return key as SecretKey
}