A Bullet-Proof Approach to Storing Sensitive User Data in React Native
By: Marcel Kalveram
Tags: React Native

Keeping data on mobile devices secure is crucial if you’re working on a product that stores sensitive information about your user.

But did you know that by default, the local storage mechanism in React Native (AsyncStorage) is unencrypted?

On iOS, the main storage library for React Native is based on the Apple File System, using FileManager to read and store files on the device unencrypted.

On Android, AsyncStorage uses either the key-value store RocksDB or SQLite, based on which one is available. The data in there is unencrypted.

TL;DR — just show me the code! The repository is available here. Feel free to fork and raise issues:

https://github.com/marcelkalveram/react-native-bulletproof-redux-provider

A hybrid approach to safely store user data

Of course, there is a way on both platforms to store data more securely: Keychain and Keystore, both abstracted away and nicely encapsulated by the powerful library react-native-keychain .

But none of them is made for storing huge chunks of data. Here are two bits of context from their respective documentation:

Use Keychain to securely store small chunks of data on behalf of the user. The Keystore lets you store cryptographic keys in a container to make it more difficult to extract from the device.

As you can see, not a place where you’d store huge blobs of sensitive data.

With that in mind, how would we go about combining Keychain/Keystore and AsyncStorage to create a hybrid that benefits from the best of both worlds: lots of storage space and ultra-secure encryption?

Here’s a high-level idea. We’ll go into the implementation details later:

Create a secure key using react-native-keychain . That’s the key we’ll use to encrypt our data. 🔑 Turn our store data into an easily encrypt-able format, ie. using JSON.stringify() . Encrypt our data using the generated secure key 🔐 Write the encrypted blob of data to our unencrypted store.

This turns your unencrypted AsyncStorage into a safe vault that can only be unlocked by the person owning that key. In most cases and unless someone got their phone stolen and compromised that’s the primary user of the device.

Adding Redux to the mix

While this approach can be applied to any storage solution, I’m going to explain how this can be achieved using Redux, Redux Persist and Redux Persist’s “Transform Encrypt” plugin.

Leveraging redux-persist

With redux-persist , implementing this encryption flow gets a whole lot simpler because the encryption/decryption mechanism can be easily plugged in via the redux-persist-transform-encrypt plugin.

The crux here is to not use any randomly generated static key (defined at build time) and could potentially be reverse-engineered and accessed by intruders. Instead, we use a random key that:

we generate at runtime, when the app gets launched for the first time.

we store in our secure Keystore / Keychain vault.

/ vault. we reuse for every subsequent use of our app.

The fact that the secure random key has a high degree of randomness, is not available at build time and can’t be easily guessed or recreated makes the encrypted data in your AsyncStorage much safer, and completely useless to anyone without it.

Putting it into practice

It’s easier to understand this flow by visualising it at a slightly higher level of abstraction. I’ve split this process up into two “Gate” components, an idea I shamelessly stole from redux-persist (which contains a so-called PersistGate, which we’re also going to talk about briefly later).

The “Gate” here implies that the component won’t render its children before it hasn’t completed its internal state logic.