Android Fingerprint Authentication

Let’s be frank, I don’t like authenticating, you don’t like authenticating, users don’t like authenticating
Every time my phone asks me to enter a password, even a pattern, I feel like I want to punch an elephant.

What we (users) normally want is to take out our device and use it immediately, we want access to our information in no-time, anything else is just not good enough for us. 
How many of us have disabled in the past the lock screen of our device and, left it completely open and vulnerable to undesired hands just because we did not want to spend time entering a password. As simple as that, we are very lazy people.
If we’re capable of compromising our entire device’s security just to have a slightly faster user experience, image what we’re capable of doing to an app that pops a dialog to enter a password every time it decides we need to be authenticated to perform a certain action.

Android fingerprint authentication was introduced on M release and Nexus phones — today available in many more device brands — to solve that problem. It encourages users to use authentication in a very non-disturbing and easy way so that we don’t need to compromise between security and user experience.

Imagine you are developing an app that requires to perform some critical operation like, payment. You want to be sure that your user is authenticated prior wiring any kind of money, right? 
Fingerprint authentication is your ally. Let’s go through the main elements you will need to achieve such functionality.

The key

Create a key and bind it to a fingerprint authentication.

// Get an instance to the key generator using AES
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
int purpose = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT;
// to make it harder, byte padding
int padding = KeyProperties.ENCRYPTION_PADDING_PKCS7;
// Init the generator
keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, purpose)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
// key allowed only when user is authenticated
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(padding)
.build());
// generate the key
keyGenerator.generateKey();

The KEY_NAME (aka key alias) is used to reference and find the generated key. The call to setUserAuthenticationRequired(true) binds the key to the user authentication.
You could also set the time validity of the key since the last authentication using setUserAuthenticationValidityDurationSeconds(int) in the builder object.

We can now get the key from the key store using the KEY_NAME as reference.

@Nullable
public SecretKey getKey(@NonNull String keyName) {
mKeyStore.load(null);
return mKeyStore.getKey(keyName, null /* password */);
}

The Cipher

The cipher is the component that provides the functionality for encryption and decryption. We can create a cipher using the Cipher.getInstance(String) method. The String identifies the transformation the cipher will perform e.g., DES/CBC/PKCS5Padding.

Cipher mCipher = 
Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
// init the cipher
SecretKey secretKey = (SecretKey)keyStore.getKey(keyName, password);
mCipher.init(Cipher.ENCRYPT_MODE, secretKey);

The Authentication

The authentication is fairly simple, just get a reference to the FingerprintManager and call the authenticate() using an initialized cipher.

mFingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
mFingerprintManager.authenticate(
new FingerprintManager.CryptoObject(mCipher), mCancellationSignal,
0 /* flags */,
new FingerprintManager.AuthenticationCallback() {
public void onAuthenticationSucceeded() {
// do the payment here...
}
}, null);

The Pitfalls

The call mCipher.init() will throw InvalidKeyException when the key is no longer valid because, e.g. new fingerprints have been added or removed since last authentication, device rebooted, etc. 
In this case, it is good practice to fallback to e.g. password authentication to verify your user is who he/she should be. 
On succesful password authentication, you could recreate the key calling mKeyGenerator.generateKey() and continuing with fingerprint authentication for subsequent operations.

The Code

What discussed in this post is implemented one way or another inside the FingerLock library available on GitHub. 
The library hides all the complexity of creating and initializing a key store, a cipher, checking key validity, etc. and reduces fingerprint authentication to a handful of calls and callbacks.