Storing application secrets in Android's credential storage

This article describes how to talk to the system keystore daemon directly and store app-specific secrets in the system credential storage. It will introduce private API's, not available via the Android SDK and some OS services implementation details. Those may change at any time, and are not guaranteed to work. While the techniques described have been tested on a few different devices and OS versions (2.1 to 4.0), there are no guarantees. Use caution if you decide to implement them in a production app.

As described in previous articles, Android has had a system-level credential storage since Donut (1.6). Up until ICS (4.0), it was only used by the VPN and WiFi connection services to store private keys and certificates, and a public API was not available. ICS introduced a public API  and integrated the credential storage with the rest of the OS. However, while the underlying implementation is able to store arbitrary data owned by any app, the ICS API only allows us to store private keys and certificates owned and managed by the OS. While this is could be seen as  a good thing -- it allows for tighter control over who can access what keys, it is also rather limiting. Third party apps often need to store sensitive information, such as passwords, authentication tokens and encryption keys the app uses, but the KeyChain API doesn't allow this. As mentioned in the password-based encryption article, one alternative is to derive a key from a user-supplied password and use it to encrypt sensitive data private to an application. While this works, it requires the user to remember one more password, and increases application complexity -- developers need to implement services, not directly related to app functionality; services that should ideally be provided by the system. The next Android version, reportedly just around the corner, might expose such services via public API's, but you could use them now, if you are willing to take the risk of your app breaking when Jelly Bean comes along.

Android's credential storage is implemented as a native Linux service (daemon), with a few extra layers on top of it that make it available to the framework. Let's quickly review what we know about the keystore daemon (described in more detail here):
  • it's a native daemon, started at boot
  • it provides a local control socket to allow apps and system services to talk to it
  • it encrypts keys using an AES 128 bit master key
  • encrypted keys are stored in /data/misc/keystore, one file per key
  • the master key is derived from the device unlock password or PIN
  • it authorizes administration commands execution and key access based on caller UID
Here's a quick summary of the available commands and who is permitted to execute them:

Keystore daemon commands
Command Description Allowed UIDs Parameters
test Check that the key store is in a usable state anyone but root, vpn and wifi none
get Get unencrypted key anyone (*1) key name
insert Add or overwrite key anyone but root, vpn and wifi key name and value
del Delete a key anyone but root, vpn and wifi (*1) key name
exist Check if a key exists anyone but root, vpn and wifi (*1) key name
saw List keys with the specified prefix anyone but root, vpn and wifi (*1) key prefix
reset Reset the key store system none
password Change the key store password system new password
lock Lock the key store system none
unlock Unlock the key store system none
zero Check if the key store is empty system none
*1 Only keys created with the same UID are visible/accessible

As you can see from the table above, once the credential storage is initialized and unlocked, any app can add, delete, list and get keys. Each key is bound to the UID of the process that created it, so that apps cannot access each other's keys or the system ones. Additionally, even system apps cannot see app keys, and root is explicitly prohibited from creating or listing keys. Thus, if the API were public user apps could use the credential storage to securely store their secrets, as long as it is unlocked. Unlocking, however, requires a system permission. On ICS, the credential storage is unlocked when you enter your device unlock pattern, PIN or password, so in practice the keystore daemon will be already in an unlocked state by the time your app starts. On pre-ICS devices the device unlock password and the credential storage protection password are separate, so unlocking the device has no effect on credential storage state. Fortunately, Android provides a system activity that can unlock the key store. All we have to do is send an intent with the proper action to start the unlock activity. The action is however, slightly different on pre-Honeycomb and Honeycomb/ICS devices, so we need to check the Android version, before sending it:

try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
startActivity(new Intent("android.credentials.UNLOCK"));
} else {
startActivity(new Intent("com.android.credentials.UNLOCK"));
}
} catch (ActivityNotFoundException e) {
Log.e(TAG, "No UNLOCK activity: " + e.getMessage(), e);
}

Note that the unlock activity is using the transparent theme, so it will look like a dialog originating from your own activity. It is, however, managed by the system, so your app will be paused and resumed only after the unlock activity finishes. You need to handle this in your activity's code (you can't use startActivityForResult() though, since the unlock activity doesn't call setResult()). Additionally, if you don't have a device (or credential storage on pre-ICS devices) password set up, you will be prompted to set one. Control will be returned to your app only after you have set and confirmed an unlock password and initialized the credential storage.

Now that the keystore is unlocked, we can try to actually use it. As briefly mentioned above, it uses a local control socket for IPC, and the protocol is rather simple: a single letter command, followed by the length and value of any parameters (up to two). The protocol is already implemented in the android.security.KeyStore class, which is however hidden from non-system  applications. The reason for not exposing this API given in the JavaDoc comment is that 'it assumes that private and secret key bytes are available and would preclude the use of hardware crypto'. This is a very valid comment: in the current implementation keys are exported and imported as unencrypted blobs. If the keys were protected by a hardware device, the API would have to return some sort of an opaque key handle, since the actual key material would not be available, or would only be exportable if wrapped with another key. If the next Android version introduces hardware cryptography support, the API would have to change dramatically. Having said that, we want to use the keystore now, so we will ignore the warning and go ahead. Since the KeyStore is hidden we cannot import it directly, but we can call it using reflection. This is easy enough to do, but somewhat cumbersome. As the class doesn't really have any dependencies it is easier to copy it in our project, adding a few minor modifications to get it to compile (see sample code). Once this is done, we can list, add and get keys:

KeyStore ks = KeyStore.getInstance();
// get the names of all keys created by our app
String[] keyNames = ks.saw("");

// store a symmetric key in the keystore
SecretKey key = Crypto.generateKey();
boolean success = ks.put("secretKey1", key.getEncoded());
// check if operation succeeded and get error code if not
if (!success) {
int errorCode = ks.getLastError();
throw new RuntimeException("Keystore error: " + errorCode);
}

// get a key from the keystore
byte[] keyBytes = ks.get("secretKey1");
SecretKey key = new SecretKeySpec(keyBytes, "AES");

// delete a key
boolean success = ks.delete("secretKey1");

As you can see from the code above, using the credential storage is pretty straightforward. You save keys by giving them a name (used as part of the file name the encrypted blobs are saved into), and then use that name to retrieve or delete them. The UID of the process that created the key is also a part of the file name, and thus key names only need to be unique within your application. One thing to note is that KeyStore methods that don't return a value (key name(s) or bytes), return a success flag, so you need to make sure you check it. In case of an error a more detailed error code can be obtained by calling getLastError(). All error codes are defined in the KeyStore class, but you are most likely to encounter PERMISSION_DENIED (if you try to call one of the methods reserved for the system user) or KEY_NOT_FOUND (if you try to access a non-existing key).

Check the sample project for a full app that generates an AES key, encrypts some data, then stores the key in the system credential storage and later retrieves it in order to decrypt the data. It generates and saves a new key each time you press 'Encrypt' and you can see the stored keys in the list view. Press the 'Reset' button to delete all keys created by the app. Note that the KeyStore class used is not compatible with the original Donut (Android 1.6) credential storage implementation, but it should work with all (public) subsequent versions. Here's how the app's screen looks like. Full code is, as usual, on github.


Besides keys you can store any sensitive information your app needs such as login passwords or tokens. Since decrypting the files on disk requires a key derived from the unlock password (or a dedicated password on pre-ICS devices), your secrets cannot be extracted even by apps with root access, or someone with physical access to the device (unless they know the password, of course). The master encryption key, however, is not tied to the device (like in iOS), so it is possible to copy the encrypted key files and perform a brute force attack on a different, more powerful machine(s).

You can experiment with other KeyStore API's, but most of those will result in a PERMISSION_DENIED when called from a non-system app. On ICS, there is also a public intent (action: com.android.credentials.RESET) that resets the credential storage, so you could prompt the user to clear it from your app, if necessary. Note that this will delete all stored data (keys, certificates, etc.), not just the ones your app created, so use with caution.

As a final warning, the code presented in this post does rely on private API's and OS implementation details, so it might break with the next Android version, or even not work on all current devices. Keep this in mind if you decide to use it in a production app.

Comments