From Punched Cards to Prompts
AndroidIntroduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
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+.
To get started with the AndroidX Security library, add the following dependency to your module-level build.gradle
file.
implementation "androidx.security:security-crypto:1.0.0-alpha02"
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 EncryptedSharedPreferences
instance.
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 Context#getSharedPreferences
or Activity#getPreferences
, we’ll need to create our own instance of EncryptedSharedPreferences
.
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"
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.
Android’s 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.
Introduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
Jetpack Compose is a declarative framework for building native Android UI recommended by Google. To simplify and accelerate UI development, the framework turns the...
Big Nerd Ranch is chock-full of incredibly talented people. Today, we’re starting a series, Tell Our BNR Story, where folks within our industry share...