Keys, Credentials and Storage on Android

Keys, Credentials and Storage on Android

Keys, Credentials and Storage on Android

Keys, Credentials and Storage on Android

Keys, Credentials and Storage on Android
Keys, Credentials and Storage on Android
Keys, Credentials and Storage on Android Keys, Credentials and Storage on Android Keys, Credentials and Storage on Android Keys, Credentials and Storage on Android Keys, Credentials and Storage on Android

Keys, Credentials and Storage on Android

In the previous post on Android user data security, we looked at encrypting data via a user-supplied passcode. This tutorial will shift the focus to credential and key storage. I’ll begin by introducing account credentials and end with an example of protecting data using the KeyStore.

Often
when working with a third-party service there will be some form of
authentication required. This may be as simple as a /login endpoint
that accepts a username and password. It would seem at first that a simple
solution is to build UI that asks the user to log in, then capture and
store their login credentials. However, this isn’t the best practice because
our app shouldn’t need to know the credentials for a 3rd party account. Instead, we can use the
Account Manager, which delegates handling that sensitive information
for us.

Account
Manager

The Account Manager is a centralized helper
for user account credentials so that your app does not have to deal
with passwords directly. It often provides a token in place of the
real username and password that can be used to make
authenticated requests to a service. An example is when requesting an OAuth2 token. Sometimes all the required information is already
stored on the device, and other times the Account Manager will need
to call a server for a refreshed token. You may have seen the Accounts section in your device’s Settings for various apps. We can get that list of available accounts like this:

The code will require the android.permission.GET_ACCOUNTS permission. If you’re looking for a specific account, you can find it like this:

Once
you have the account, a

token
for
the account
can
be
retrieved
by calling the getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler) method. The token can then be used to make authenticated API requests to a service. This could be a RESTful API where you pass in a token parameter during an HTTPS request, without having to ever know the user’s private account details.

Because
each service will have a different way of authenticating and storing
the private credentials, the Account Manager provides authenticator
modules for a 3rd party service to implement. While Android has
implementations for many popular services, it means you can write
your own authenticator to handle your app’s account authentication
and credential storage. This allows you to make sure the credentials are
encrypted. Keep in mind, this also means that credentials in the
Account Manager that are used by other services may be stored in
clear text, making them visible to anyone who has rooted their
device.

Instead
of simple credentials, there are times when you will need to deal with a key
or a certificate for an individual or entity, for example, when a third
party sends you a certificate file which you need to keep. The most common scenario is when an app needs to authenticate to a private organization’s server. In the
next tutorial, we will be looking at using certificates for
authentication and secure communications, but I still want to address
how to store these items in the meantime. The Keychain API was
originally built for that very specific use—installing a private
key or certificate pair from a PKCS#12 file.

The Keychain

Introduced
in Android 4.0 (API Level 14), the Keychain API deals with key
management. Specifically, it works with PrivateKey and
X509Certificate objects and provides a more secure container than using your app’s data storage. That’s because permissions for private keys only allow for your own app
to access the keys, and only after user authorization. This means
that a lock screen must be set up on the device before you can make
use of the credential storage. Also, the objects in the keychain may be
bound to secure hardware, if available. The code to install a certificate is as follows:

The
user will be prompted for a password to access the private key and an
option to name the certificate. To retrieve the key, the following code presents UI that lets the user choose from the list of installed keys.

Once the choice is made, a string alias name is returned in the alias(final String alias) callback where you can access the private key or
certificate chain directly.

Armed with that knowledge, let’s now see how we can use the credential storage to save your own sensitive data.

The KeyStore

In
the previous tutorial, we looked at protecting data via a
user-supplied passcode. This kind of setup is good, but app
requirements often steer away from having users login each time and
remember an additional passcode. That’s where the KeyStore API can be
used. Since API 1, the KeyStore has been used by the system to store WIFI
and VPN credentials. As of 4.3 (API 18), it allows working with your
own app-specific asymmetric keys, and in Android M (API 23) it can store an AES
symmetric key.
So while the API doesn’t allow storing
sensitive strings directly, these keys
can be stored, and then used to encrypt strings. 

The benefit to
storing a key in the KeyStore is that it allows keys to be operated
on without exposing the secret content of that key; key data does not
enter the app space. Remember that keys are protected by permissions
so that only your app can access them, and they may additionally be
secure hardware-backed if the device is capable. This creates a
container that makes it more difficult to extract keys from a device. 

Generate a New Random Key

So for this example, instead of generating an AES key from a user-supplied
passcode, we can auto-generate a random key that will be protected in
the KeyStore. We can do this by creating a KeyGenerator instance, set
to the "AndroidKeyStore" provider.

Important parts to look at here are the .setUserAuthenticationRequired(true) and .setUserAuthenticationValidityDurationSeconds(120) specifications. These require a lock screen to be set up and lock the key until the user has authenticated. Looking at the documentation for .setUserAuthenticationValidityDurationSeconds(), you will see that it means the key is only available a certain number of seconds from password authentication, and that passing in -1 requires finger print authentication every time you want to access the key. Enabling the requirement for authentication also has the effect of revoking the key when the user removes or changes the lock screen. Because storing an unprotected key along side the encrypted data is like putting a house key under the doormat, these options attempt to protect the key at rest in the event a device is compromised. An example might be an offline data dump of the device. Without the password being known for the device, that data is rendered useless.

The .setRandomizedEncryptionRequired(true) option enables the requirement that there is enough randomization (a new random IV each time) so that if the exact same data is encrypted a second time around, that encrypted output will still be different. This prevents an attacker from gaining clues about the ciphertext based on feeding in the same data. Another option to note is setUserAuthenticationValidWhileOnBody(boolean remainsValid), which locks the key once the device has detected it is no longer on the person.

Encrypting Data

Now
that the key is stored in the KeyStore, we can create a method that
encrypts data using the Cipher object, given the
SecretKey. It will return a HashMap containing the encrypted data,
and a randomized IV that will be needed to decrypt the data. The encrypted data, along with the IV, can then be saved to a file or into the shared preferences.

Decrypting to a Byte Array

For
decryption, the reverse is applied. The Cipher object is initialized
using the DECRYPT_MODE constant and a decrypted byte[] array is
returned.

Testing the Example

We can now test our example!

Using RSA Asymmetric Keys for Older Devices

This
is a good solution to store data for versions M and higher, but what
if your app supports earlier versions? While AES symmetric keys are
not supported under M, RSA asymmetric keys are. That means we can use
RSA keys and encryption to accomplish the same thing. The
main difference here is that an asymmetric keypair contains two keys,
a private and a public key, where the public key encrypts the data
and the private key decrypts it. A KeyPairGeneratorSpec is passed
into the KeyPairGenerator that is initialized with KEY_ALGORITHM_RSA
and the "AndroidKeyStore" provider.

To
encrypt, we get the RSAPublicKey from the keypair and use it with the
Cipher object. 

Decryption is done using the RSAPrivateKey object.

One
thing about RSA is that encryption is slower than it is in AES. This is
usually fine for small amounts of information such as when you’re securing
shared preference strings. If you find there is a performance problem
encrypting large amounts of data, however, you can instead use this example to
encrypt and store just an AES key. Then, use that faster AES
encryption that was discussed
in the
previous tutorial
for the rest of your data. You can generate a new AES key and convert it to a
byte[] array that is compatible with this example.

To
get the key back from the bytes, do this:

That
was a lot of code! To keep all of the examples simple, I have
omitted
thorough exception handling. But remember that for your production
code, it’s not recommended to simply catch all Throwable cases
in one catch statement.

Conclusion

This
completes the tutorial on working with credentials and keys. Much of
the confusion around keys and storage has to
do with the evolution of the Android OS, but you can choose which
solution to use given the API level your app supports. 

Now that we
have covered the best practices for securing data at rest, the next
tutorial will focus on securing data in transit.