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:
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);
}
}
I've found a method to escape the Catch 22!
You do it like this:
You try{
.init
yout Cipher
as usual
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!
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With