Saturday, December 2, 2017

java.security.UnrecoverableKeyException: Failed to obtain information about private key

When this error happens and why?

Ans: When loading Android keys and storing public key from Keystore, this error may happen if the state is locked or uninitialized.

Error generating portion code is given below:

@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 has 10 response code. They are
// 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 has 3 states. They are UNLOCKED, LOCKED, UNINITIALIZED
NO_ERROR is only happened when the state is UNLOCKED. For your upgrading case the state is LOCKED or UNINITIALIZED for first time, so the error is happened only once.

State Checking code is given below:

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);
    }
}
Resource Link:
  1. AndroidKeyStoreProvider java class
  2. KeyStore java class

UPDATE:

From your error log, it is now clear that
W/System.err Caused by: android.security.KeyStoreException: Invalid key blob
this is the main issue which is caused when user tries to UNLOCK from LOCK/UNINITIALIZED. It is by default defined as 30 secs for timing. This problem is it's API related implementation issue.
/**
 * 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;
For encryption/decryption some data with the generated key only works if the user has just authenticated via device credentials. The error occurs from
// 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.
Actual error is thrown from here. Your error is generated from InvalidKeyException.

Solution:

You have to remove the InvalidKeyException class from the catch argument. This will still allow you to check for InvalidKeyException. After checking you have to try for second time with code so that the problem is not shown in eye but doing 2 times checking it may solve your issue. I have not tested the code but should be like below:
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;
        }
    }
}

Resource Link:

  1. MainActivity.java
  2. android.security.KeyStoreException: Invalid key blob

No comments:

Post a Comment