Sometimes it is necessary to validate a user’s identity in order to provide access to sensitive information within an app. The traditional mechanism of a username and password has always been rather clunky on mobile because of small screens and soft keyboards. However with the arrival of hardware such as fingerprint scanners arriving on devices, there are some new much more user-friendly mechanisms that we can use for authentication. In this series we’ll explore how to implement these within our apps.
Previously we looked at how to perform a fingerprint scan:
Before we dive in to the code let’s have a very brief overview of the cryptography that’s at the core of this. We briefly discussed the Cipher in the previous article and this is a mechanism for encrypting data. The cipher is a well defined, public algorithm and it is the key that is used by the cipher to encrypt the data which is the secure part. The simplest kind of key is a symmetric key. With a symmetric key if you pass any given data to the cipher and encrypt using the symmetric key and then pass the encrypted data through the same cipher with the same key, then the original data will be returned. However we’re not actually using the cipher in this way as the FingerprintManager user some Android-specific key generation to provide a secure authentication mechanism.
The way this works is with how we define the key itself. We use an Android-specific key generation and specify that the key requires user authentication before it can be used. When we generate this key it gets stored in a KeyStore, and we receive a copy which we use to initialise the cipher. The cipher is then passed to the FingerprintManager for authentication, and when there is a successful scan the FingerprintManager accesses the associated key from the KeyStore, flags it with the successful user authentication, and only then can it be used to encrypt data using the cipher.
If we try and encrypt using the cipher before the key has been flagged for successful user authentication then “android.security.KeyStoreException: Key user not authenticated” exception will be thrown. Although we should not get the “success” callback until the scan has completed successfully, validating the cipher in this way means that we can have a high degree of confidence that nothing is interfering with our authentication request because the ability to set the required flag on the key is only available to the OS and not any apps running on top.
So let’s take a look at the code to actually do all of this:
public final class KeyTools { private static final String PROVIDER_NAME = "AndroidKeyStore"; private static final String USER_AUTH_KEY_NAME = "com.stylingandroid.identity.USER_AUTH_KEY"; private static final String ALGORITHM = KeyProperties.KEY_ALGORITHM_AES; private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; private static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; private static final String TRANSFORMATION = ALGORITHM + "/" + BLOCK_MODE + "/" + PADDING; private final KeyStore keyStore; private final Map<String, KeySpecGenerator> generators; public static KeyTools newInstance() throws KeyToolsException { KeyStore keyStore; try { keyStore = KeyStore.getInstance(PROVIDER_NAME); keyStore.load(null); } catch (Exception e) { throw new KeyToolsException("Error initializing keystore: ", e); } Map<String, KeySpecGenerator> generators = new ArrayMap<>(); generators.put(USER_AUTH_KEY_NAME, new UserAuthKeySpecGenerator(BLOCK_MODE, PADDING)); return new KeyTools(keyStore, Collections.unmodifiableMap(generators)); } private KeyTools(KeyStore keyStore, Map<String, KeySpecGenerator> generators) { this.keyStore = keyStore; this.generators = generators; } public Cipher getUserAuthCipher() throws KeyToolsException { try { return getCipher(USER_AUTH_KEY_NAME); } catch (Exception e) { throw new KeyToolsException("Error creating cipher", e); } } . . . private KeyGenParameterSpec getKeyGenParameterSpec(String keyName) { return generators.get(keyName).generate(keyName); } public static class KeyToolsException extends Exception { public KeyToolsException(String detailMessage, Throwable throwable) { super(detailMessage, throwable); } }
This is the basic skeleton of our cipher creation. I have deliberately separated out the actual key specification generation (i.e. the bit where we declare that the key requires user authentication) in to UserAuthKeySpecGenerator to show how we can keep this aspect completely separate to our cipher and key generation code. The significance of this will become apparent later in the series. However, we store this in a Map keyed with the name that we’ll also use to store the key within the KeyStore: USER_AUTH_KEY_NAME = "com.stylingandroid.identity.USER_AUTH_KEY"
We then have a utility method to return the correct cipher named getUserAuthCipher()
which calls getCipher with the key name as an argument.
Finally we have a method to look up a KeyGenParameterSpec based upon a given key, and a define a generic exception wrapper: KeyToolsException.
So let’s now take a look at the getCipher()
method:
private Cipher getCipher(String keyName) throws KeyToolsException, UserNotAuthenticatedException, IllegalStateException { try { return getCypher(keyName, true); } catch (UserNotAuthenticatedException | IllegalStateException e) { throw e; } catch (Exception e) { throw new KeyToolsException("Error creating cipher for " + keyName, e); } } private Cipher getCypher(String keyName, boolean retry) throws KeyToolsException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, NoSuchPaddingException, InvalidKeyException { try { SecretKey secretKey = getKey(keyName); Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, secretKey); return cipher; } catch (KeyPermanentlyInvalidatedException e) { if (retry) { keyStore.deleteEntry(keyName); return getCypher(keyName, false); } else { throw e; } } } private SecretKey getKey(String keyName) throws KeyStoreException, KeyToolsException, UnrecoverableKeyException, NoSuchAlgorithmException { if (!keyStore.isKeyEntry(keyName)) { createKey(keyName); } return (SecretKey) keyStore.getKey(keyName, null); }
First we look up to see if we already have a key of the given name in the KeyStore. If not we create one. We then get the key and get an instance of an AES Cipher with CBC block mode and PKCS7 padding.
Advanced Encryption Standard (AES) is the actual encryption algorithm that will be used. It is a block cipher which means that it will encrypt the data as a series of fixed size blocks. Cipher Block Chaining (CBC) is a mode of operation whereby each block is XORed with the encrypted, previous block before it is encrypted. In essence this add dependencies between the blocks, with each one being dependent on all of those preceding it in the chain. Finally Public Key Cryptography Standard #7 (PKCS7) is a padding method. The blocks must all be of the same size and if the data does not exactly fit in to a number of whole blocks then the last block will need to be padded to produce a complete block. PKCS7 simply defines how the padding bytes will be generated (the actual specifics aren’t important to us, we just need to be consistent).
Next we’ll look at our createKey()
method:
private void createKey(String keyName) throws KeyToolsException, IllegalStateException { try { KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM, PROVIDER_NAME); KeyGenParameterSpec spec = getKeyGenParameterSpec(keyName); keyGenerator.init(spec); keyGenerator.generateKey(); } catch (InvalidAlgorithmParameterException e) { if (e.getCause() instanceof IllegalStateException) { throw (IllegalStateException) e.getCause(); } throw new KeyToolsException("Error creating key for " + keyName, e); } catch (Exception e) { throw new KeyToolsException("Error creating key for " + keyName, e); } }
We first get an instance of a KeyGenerator by passing the name of the algorithm (AES) and the provider name – in this case we use “AndroidKeyStore” because it’s what the FingerprintManager uses.
Next we obtain the KeyGenParameterSpec instance for our key. As we only defined the one earlier this will actually be UserAuthKeySpecGenerator which we’ll look at in a moment.
We then initialise the key generator using this key generation specs, and then generate the key itself which will create it and store it in the KeyStore.
All that’s left is to look at UserAuthKeySpecGenerator:
class UserAuthKeySpecGenerator implements KeySpecGenerator { private final String blockMode; private final String padding; UserAuthKeySpecGenerator(String blockMode, String padding) { this.blockMode = blockMode; this.padding = padding; } @Override public KeyGenParameterSpec generate(String keyName) { return new KeyGenParameterSpec.Builder(keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(blockMode) .setEncryptionPaddings(padding) .setUserAuthenticationRequired(true) .build(); } }
This gets initialised with the required block and padding modes (CBC and PKCS7 respectively), and the key specification is created in the generate method. This basically creates rules for how we want to generate the key – we give it the name, the purpose of the key, block mode, padding mode, and finally we tell it that user authentication is required before they key can be used. This is actually the important bit because it means that the key will not work until the user has been authenticated.
It’s actually just this class which is specific to what we need for the FingerprintManager authentication – that’s the reason I wanted to keep it separate. Pretty well all of KeyTools is just standard code to generate a Key and Cipher, but it’s only the KeyGenParameterSpec that we create in UserAuthKeySpecGenerator which is specific to our needs.
So, now that we have covered how authentication using FingerprintManager actually works, it would seem that we’re complete. However we can actually implement some different security using many of the techniques that we’ve covered here. n the next article we’ll take a look at how we can put secure content behind the standard Android lock screen.
There is no additional code for this article, but the code we have been looking at is available here.
© 2015, Mark Allison. All rights reserved.
Copyright © 2015 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.