Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UserNotAuthenticatedException during FingerprintManager.authenticate()

I have an encrypted password stored in the Android KeyStore.

I want to decrypt that password by authenticating the user using the fingerprint API.

As far as I understand, I have to call the FingerprintManager.authenticate(CryptoObject cryptoObject) method to start listening for the fingerprint result. The CryptoObject parameter is created like this:

public static Cipher getDecryptionCipher(Context context) throws KeyStoreException {
    try {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        SecretKey secretKey = getKeyFromKeyStore();
        final IvParameterSpec ivParameterSpec = getIvParameterSpec(context);

        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        return cipher;

    } catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | UnrecoverableKeyException | CertificateException | InvalidAlgorithmParameterException | InvalidKeyException e) {
        e.printStackTrace();

    }

    return null;
}

Cipher cipher = FingerprintCryptoHelper.getDecryptionCipher(getContext());
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
fingerprintManager.authenticate(cryptoObject, ...);

The method getDecryptionCipher() works correctly until the cipher.init()call. On this call I get an UserNotAuthenticatedException, because the user is not authenticated for this secretKey. Which makes sense somehow. But isn't this a loop, impossible to fulfill:

  • To authenticate the user, I want to use his/her fingerprint
  • To listen for his/her fingerprint, I need to init the Cipher, which in return needs an authenticated user

What's wrong here??

EDIT:

I work with the emulator (Nexus 4, API 23).

Here's the code I use to create the key.

private SecretKey createKey() {
    try {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
        keyGenerator.init(new KeyGenParameterSpec.Builder(
                KEY_NAME,
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
        )
                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                .setUserAuthenticationRequired(true)
                .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                .build());
        return keyGenerator.generateKey();
    } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
        throw new RuntimeException("Failed to create a symmetric key", e);
    }
}
like image 635
muetzenflo Avatar asked Aug 22 '16 11:08

muetzenflo


1 Answers

I've found a method to escape the Catch 22!

You do it like this:

You try{ .init yout Cipher as usual

  1. if there's no UserNotAuthenticatedException (because user WAS authenticated within validity period of your key i.e. because he unlocked his device a few seconds ago) then do your encrypt/decrypt routine. The end!

  2. you caught UserNotAuthenticatedException - run FingerprintManager.authenticate workflow with null (yes!) CryptoObject, then in onAuthenticationSucceeded callback init your cipher again (yes!), but this time it won't throw UserNotAuthenticatedException and use this initialized instance to encrypt/decrypt (the callback returns null as we called it with null CryptoObject, so we can't use this). The end!

As simple as that...

But it took me two days to find this method by trail and error. Not to mention - it seems all authentication examples present online are wrong!

like image 127
ssuukk Avatar answered Sep 21 '22 12:09

ssuukk