Organized Secure Storage — EncryptedSharedPreferences

Introducing DataStore — A secure approach to organizing your prefs usage

Daniel Ochoa
VMware 360
4 min readMay 25, 2020

--

Android recently released a couple crypto application program interfaces (APIs) as part of Android Jetpack which are EncryptedSharedPreferences and EncryptedFile. We’ll be focusing on EncryptedSharedPreferences and show how to build a generic abstraction on top that allows the developer to not only provide secure storage of arbitrary data, but also re-think the usage of preferences.

Storing data

There are a few different ways of storing data depending on the use case. Here are a few to mention:

SharedPreferences

SharedPreferences provides an abstraction over a flat file on disk that allows for writing key/value pairs to a clear text file in your app’s directory. Under normal situations this is perfectly secure as your app’s directory is protected by Android’s sandbox environment.

SQLite

For large volumes of data that need to be queried often, or used as part of UI rendering, the developer will likely be using a SQLite database. The recommended abstraction over SQLite is to use Room rather than SQLite directly but it’s still SQLite behind the scenes.

What is EncryptedSharedPreferences?

This is a version of SharedPreferences that encrypts the keys and values before storing them to the flat file on disk. All of the methods used with SharedPreferences are intact so it’s a seamless integration.

Why is EncryptedSharedPreferences necessary?

If an app directory is protected against access, then why does the developer need an encrypted version? If an app is installed on an emulator or rooted device, it has instant access to a lot of your configuration settings, or worse, it may store some serialized objects as JSON into SharedPreferences. Now it’s exposed some internals of the app that the developer didn’t want exposed. Or maybe the developer works for a company that can’t afford to have any data at rest unencrypted. Here comes EncryptedSharedPreferences to the rescue.

Overview

Here’s a quick overview of what I will be going over today.

  • Setup
  • DataStore
  • A full implementation of DataStore

Setup

Add the following to your build.gradle file.

Note: Keep in mind you’ll need to change your minSdkVersion to 23 due to an internal dependency on the Tink crypto library.

implementation("androidx.security:security-crypto:1.0.0-rc02")

DataStore

With SharedPreferences the typical developer will place all of the app’s key-value pairs into a single class that becomes unmanageable. DataStore is a generic abstraction over EncryptedSharedPreferences that gives the developer a way to organize our preferences into meaningful collections.

Let’s talk about what we have above.

annotation class Key

This allows a class to define the key where its data will be stored and allows for one less argument to be passed into the restore/save methods.

sharedPrefs

This is our instance of EncryptedSharedPreferences that is initialized with the recommended defaults.

restore

This method takes two arguments. The first is the class that is needed to restore which is required to have the above annotation class defined for it. The second argument is a function block that takes a nullable String (the serialized data retrieved from SharedPrefs) and produces a nullable generic T instance.

save

This method takes the instance of the class that was returned from a call to restore and a function block that takes in the first argument and produces a serialized representation that can be stored to SharedPreferences.

Wrapping this up into an implementation

First we need to settle on a serializer/de-serializer so for this example we’ll use GSON.

Quick explanation on the above code. De-serialize is reified so the generic type is kept and can be utilized by Gson() to restore the correct class type.

Now let’s pull this into an actual implementation:

And there you have it. A simple abstraction over EncryptedSharedPreferences that gives the developer secure storage and helps to organize the data into relevant classes. This is a singleton that persists the state even after the app is killed. All of its data is stored in encrypted storage and handles deserialization/serialization easily. I personally use Dagger for DI, which simplifies injecting the constructor parameters. This allows me to return a regular SharedPreference in a unit test since unit tests don’t have access to crypto libraries.

Just for giggle, let’s look at what this would have looked like using just Preferences.

Imagine this class filled with hundreds of preference keys all tracking unrelated data. Just messy.

Summary

Thanks everyone for reading and I hope you were able to take something away from this that can be put to use. If anyone is interested in what tests (unit + connected) would look like for this, please let me know in the comments. If you’ve found this useful, hit me up on Twitter!

--

--