Using the Android Keystore system to store and retrieve sensitive information

5 min read

Id like to take a moment and discuss how we can take advantage of the Android Keystore and store passwords, or any other sensitive data in it, encrypt the data, and decrypt that data right back

Lets clear some things up about the Android Keystore system before we begin. The keystore is not necessarily for passwords only, it can be for any sensitive data, and it does so in a way where it is much more difficult for attackers, or malicious/unauthorized software to get this information from us.

The Android Keystore system lets you store cryptographic keys in a container to make it more difficult to extract from the device. Once keys are in the keystore, they can be used for cryptographic operations with the key material remaining non-exportable. Moreover, it offers facilities to restrict when and how keys can be used, such as requiring user authentication for key use or restricting keys to be used only in certain cryptographic modes.

— The Google docs

An application can only edit, save, and retrieve its own keys. The concept is pretty simple, yet powerful. The app would generate or receive a private public key pair, which would then be stored in the Android Keystore system. The public key can then be used to encrypt application secrets, before being stored in the app specific folders, with the private key used to decrypt the same information when needed.

If you would like to just go straight to the full source code without reading all of the boring explanation, you can just go to the public Gist HERE


For simplicities sake, I created a simple application that demonstrates how the Android Keystore system can be used to save a password, encrypt it, display the encrypted form and decrypt it.

I wont go into any XML/layout details, it is pretty basic stuff. I will be posting the full source code at the end anyways.

Lets jump right in to the meat of this thing.

What i did was i created 2 classes. One is called EnCryptor, and the other Decryptor. The names are very self explanatory.

For this example we will be using the following encryption/decryption transformation algorithm: “AES/GCM/NoPadding”

Creating new keys

Before we can begin the encryption process, we need to come up with a name for an alias we want to use to encrypt/decrypt data with. This can be any string. As long is not an empty one. The alias is the name of the entry in which the generated key will appear in Android KeyStore.

First we need to get an instance of Androids KeyGenerator.

final KeyGenerator keyGenerator = KeyGenerator
        .getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");

We are saying that the algorithm we want to use this keygenerator for will be AES, and we want to save the keys/data in the AndroidKeyStore.

Once we have the instance we need to build a KeyGenParameterSpec using the KeyGenParameterSpec.Builder to pass in to the KeyGenerators init method.

So what is this KeyGenParameterSpec anyways?

You can think of the KeyGenParameterSpec as the properties for the keys we are going to generate. For example, lets say we wanted the key to expire after a certain amount of time, this is where we would specify that.

He is our KeyGenParameterSpec.

final KeyGenerator keyGenerator = KeyGenerator
        .getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);

final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(alias,
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
       .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .build();

So what is this saying?

The first thing is we pass in the alias we want to use. Remember, this can be anything. Next we specify the purpose, which is to Encrypt and Decrypt data.

The setBlockModes, assures us that only the block modes specified can be used to both encrypt and decrypt the data, if any other type of block mode is used, it will be rejected. You can see the different types of block modes HERE.

Since we are using the “AES/GCM/NoPadding” transformation algorith, we also tell the KeyGenParameterSpec the type of padding that should be used.

Encrypting data

Time to encrypt the data!

Once we get passed all the setup, the encryption part is pretty easy.

final KeyGenerator keyGenerator = KeyGenerator
        .getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);

final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(alias,
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
       .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .build();

keyGenerator.init(keyGenParameterSpec);
final SecretKey secretKey = keyGenerator.generateKey();

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);

First we initialize the keyGenerator with the keyGenParameterSpec. After that we generate our SecretKey.

Now that we have our secret key, we use it to initialize our Cipher object, which is what will be taking care of the actual encryption. We tell the Cipher the type of encryption transformation we will be using, we specify that we are going to be encrypting right now, and we pass in the secretKey to use for the encryption.

final KeyGenerator keyGenerator = KeyGenerator
        .getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);

final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(alias,
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .build();

keyGenerator.init(keyGenParameterSpec);
final SecretKey secretKey = keyGenerator.generateKey();

final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);

iv = cipher.getIV();

encryption = cipher.doFinal(textToEncrypt.getBytes("UTF-8"));

Next we grab a reference to the ciphers initialization vector (IV), which we will need for decryption, and we finalize the encryption with doFinal(textToEncrypt). The doFinal method returns a byte array which is the actual encrypted text.

Decrypting the data

Initiating the KeyStore

Before we start decrypting, we need an instance of the KeyStore.

keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

We use the keyStore to get our secret key using the alias we previously used when encrypting the data.

We need a SecretKeyEntry from the keyStore to grab the secretKey from.

keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore
        .getEntry(alias, null);

final SecretKey secretKey = secretKeyEntry.getSecretKey();

Remember earlier howwe set the KeyGenParameterSpecs block mode to KeyProperties.BLOCK_MODE_GCM, so that only this type of block mode can be used to decrypt the data?

Because of this we need a GCMParameterSpec where we specify an authentication tag length (has to be one of these: 128, 120, 112, 104, 96, you can read more about it in the javadocs. For this example we will go with the highest value of 128), and pass in the IV we grabbed earlier during the encryption process.

We use this GCMParameterSpec with our Cipher to initialize the decryption process

keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore
        .getEntry(alias, null);

final SecretKey secretKey = secretKeyEntry.getSecretKey();
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
final GCMParameterSpec spec = new GCMParameterSpec(128, encryptionIv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);

And like before, to get the decrypted data we do

final byte[] decodedData = cipher.doFinal(encryptedData);

To get the unencrypted string representation, we do

final String unencryptedString = new String(decodedData, "UTF-8");

And we are done!

You can find the full source code HERE

Extra: Fetching all Available aliases from the KeyStore

In order to grab all available aliases we create a getAllAliasesInTheKeystore() method inside our KeyStoreManager, which returns an array list of all available aliases in the keystore.

private ArrayList<String> getAllAliasesInTheKeystore() throws KeyStoreException {
    return Collections.list(keyStore.aliases());
}

keyStore.aliases() is what fetches the aliases from the keystore, which throws an exception if the keystore has not been initialized. It returns an Enumeration of type String with all available aliases. For ease of i use turn the enumeration into an ArrayList. But you don’t necessarily have to do the same, this is just my personal preference. To do so i use the Collections utils class.