Securely Storing Secrets in an Android Application

Sometimes an app developer needs to store secrets inside an app. For example, I am writing a healthcare app. To make it more convenient for the end user, I store the username and password in the app after a user logs in and ask them to enter a 4 digit passcode. When a user re-launches the app, or brings the app to foreground, I present a screen for her to enter the passcode and logs her in using the saved login name and password.

Storing secrets on iOS is easy: just use the KeyChain service. Keychain is a piece of Apple’s overall security framework for iOS. Secrets such as usernames and passwords are encrypted and backed up. For most apps, this is all is needed.

What about Android?

You can store secrets in Preferences after encrypting them.

But what about the keys used to encrypt the data? A general rule is you should not use any hardcoded keys because a hacker can easily decompile your code and obtain the key, thereby rendering the encryption useless. You need a key management framework, and that’s what the Android KeyStore API is designed for.

KeyStore provides two functions:

  • Randomly generates keys; and
  • Securely stores the keys

With these, storing secrets becomes easy. All you have to do is:

  • Generate a random key when the app runs the first time;
  • When you want to store a secret, retrieve the key from KeyStore, encrypt the data with it, and then store the encrypted data in Preferences.
  • When you want to read a secret, read the encrypted data from Preferences, get the key from KeyStore and then use the key to decrypt the data.

Because your key is randomly generated and securely managed by KeyStore and nothing but your code can read it, the secrets are secured.

You also need a block cipher such as AES for the encryption.

That’s all it is in theory. In practice, an API change in Android M makes this a little tricky to implement. You essentially have to handle two cases: Android versions after M (API level 23) and Android version before that.

Android M and Higher

For API level 23 and higher, the solution is relatively easy because the API generates random AES keys for you. An example can be found in the KeyGenParameterSpec API. Here’s the code.

The part worth pointing out is the call to setRandomizedEncryptionRequired(). More on that later.

IV stands for Initialization Vector. Put it simply, it’s a cryptographic feature that injects randomness to make it more secure. The important part is that the IV you use in the encryption must be the same one you use in the decryption. By default, Android forces you to use a random IV every time, but you can turn it off by calling setRandomizedEncryptionRequired() when the key is generated.

A random IV is useful if you have ongoing communication between two systems because it randomizes the data for each message, thereby making it even harder to crack. Because of the security provided by Android KeyStore, a random IV is an overkill here so I use a fixed IV instead. If you wish to use random IVs, you can just call getIV() on the cipher when you encrypt the data and use the same IV when you decrypt it.

Pre Android M

For Android API versions lower than 23, a little more work is involved. The KeyGenParameterSpec is only available in API 23 so you can’t have KeyStore generate random AES keys for you. Instead, you will need to use the KeyPairGeneratorSpec API.

As the name implies, KeyPairGeneratorSpec generates public key and private key pairs such RSA. Public key encryption is mainly for signing and authentication and is not suitable for encrypting large blocks of data, but it can be paired with a block cipher such as AES.

This is how we will use it to encrypt our data.

  • Generate a pair of RSA keys;
  • Generate a random AES key;
  • Encrypt the AES key using the RSA public key;
  • Store the encrypted AES key in Preferences.
  • Retrieve the encrypted AES key from Preferences;
  • Decrypt the above to obtain the AES key using the private RSA key;
  • Encrypt the data using the AES key;
  • Retrieve the encrypted AES key from Preferences;
  • Decrypt the above to obtain the AES key using the private RSA key;
  • Decrypt the data using the AES key

The code is shown below.

And finally, our calls to encrypt and decrypt the data.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store