The last edition of Android Programming: The Big Nerd Ranch Guide was released in October 2019. A lot has changed since then. To help...
Encrypting Shared Preferences with the AndroidX Security Library
The Android framework provides us with
SharedPreferences, which is a great way to store a small amount of key-value data. When working with sensitive data, however, it’s important to consider that
SharedPreferences stores data as plain text. We should encrypt sensitive data to keep it from prying eyes. How can we do so?
One option is to write our own encryption wrapper around
SharedPreferences using the Android KeyStore. Unfortunately, that can be pretty complicated and involve a lot of setup. Another option is to use a third-party library, which means that we need to spend time finding and vetting one. Thankfully, the AndroidX Security library was recently added and makes storing encrypted shared preferences data simple and easy for apps with a min-sdk of 23+.
On to the details
To get started with the AndroidX Security library, add the following dependency to your module-level
This is the latest version as of this writing. Be sure to check the library’s releases page to see if a newer version is available.
It’s important to note that this library is currently in the alpha phase. This means that while the functionality is stable, parts of the API may be changed or removed in future versions.
With the dependency added, the next step is to create an encryption master key and store it in the Android KeyStore. The security library provides us with an easy way to do this. The following code can be placed where you plan to create your
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
We’re given a default key generation specification,
AES256_GCM_SPEC, to use for creating the master key. While it’s recommended to use this specification, you can also provide your own
KeyGenParameterSpec if you need more control over how the key is generated.
Finally, we just need an instance of
EncryptedSharedPreferences, which is a wrapper around
SharedPreferences and handles all of the encryption for us. Unlike
SharedPreferences, which we can get from
Activity#getPreferences, we’ll need to create our own instance of
val sharedPreferences = EncryptedSharedPreferences.create( "shared_preferences_filename", masterKeyAlias, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM )
We provide the name for the shared preferences file, the
masterKeyAlias we created earlier, and a
Context. The final two arguments are the schemes with which keys and values are encrypted. They are the only options provided by the library.
Once we’ve created our
EncryptedSharedPreferences instance, we can use it just like
SharedPreferences to store and read values. Altogether, we have the following:
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec) val sharedPreferences = EncryptedSharedPreferences.create( "shared_preferences_filename", masterKeyAlias, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) // storing a value sharedPreferences .edit() .putString("some_key", "some_data") .apply() // reading a value sharedPreferences.getString("some_key", "some_default_value") // -> "some_data"
Double-checking our work
How do we know that our data is being encrypted? Let’s take a look at the contents of our shared preferences file. If you’d like to view the contents of your own shared preferences file, StackOverflow has you covered. Using the normal
SharedPreferences, this is what we have:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="some_key">some_data</string> </map>
As we can see, both the key and value are stored unencrypted. Using
EncryptedSharedPreferences, our shared preferences file resembles the following:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="ATP1ABa3NIlOap2c7iNkVaUcQmTocrnpkXl0PyI=">AU+p3hwqCgvlDOtIaawFHWVDf4rFsqghM7ivFTEJesrRp19D+zk7tqsqlGZPLAbryHI=</string> <string name="__androidx_security_crypto_encrypted_prefs_key_keyset__">12a901802f1a5d2fbc5cd3c9b545a89ca8ace8f125f8e601a8ac51929303ead8a2bbdf5428bd054360b97c1727ef93ef63b64f43ceac92156f3aee9402dd247009d9779571c6ceacfcd4e7123665cc9dd94c44c5c2c6241a8de070d365d94010f8affb6097d4b0fec1c628120a8f901c23caa03d32ecc6ce270e3cc3341e6455b87a80474b3818c3ad678faa4199a9a45078b218c89b8c5a8cbd1780a68b4f8196eb5153b6422df2bdfee6541a44089680d49f03123c0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e4165735369764b65791001189680d49f032001</string> <string name="__androidx_security_crypto_encrypted_prefs_value_keyset__">128801da6fdef289b2c6e2933c341b1b3df3b39330671d76df362ba8b0a1d807cdc9d2d4d7bc3062139377e4fa61428f3817c0e368c3196c95fdbcca3c37075e7132abae1fe0f128ceef7278a06a01e0cacf29edc1f3c1c1d37875c27c0cf5d86d0b2bb39efcac84828f664838b77aa4c406028af912e860cad8bff51aca6aaf45167d5ab5c8e57bf05db61a44089cbca7fd04123c0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e41657347636d4b65791001189cbca7fd042001</string> </map>
We can see that our key-value data has been encrypted and that two keysets were stored, as well. There is one keyset for shared preference keys and another for values. Keysets contain cryptographic keys that are used to encrypt and decrypt the shared preference data. The master key we created earlier is used to encrypt these keysets so they can be stored in shared preferences along with the data they’re for.
SharedPreferences is a useful tool to store key-value data, and when that data is sensitive, it’s a good idea to encrypt it. The recent AndroidX Security library is a welcome addition that provides us with a simple and easy-to-use interface to do so.