How to store sensitive data in a React Native app
Sensitive data refers to confidential information that, if compromised or accessed by unauthorized individuals, can result in privacy breaches, identity theft, and other harmful consequences. Sensitive data includes user credentials, API keys, and access tokens. Storing sensitive data securely is crucial for several reasons:
-
It helps protect confidential information from unauthorized access.
-
It helps preserve customer trust. Customers trust organizations with their sensitive data, expecting it to be handled securely.
-
It ensures data integrity. Storing sensitive data securely enables organizations to verify the integrity of the data and ensure that it has not been tampered with.
We can store data in React Native using the AsyncStorage system, which is an unencrypted, asynchronous, persistent, key-value storage system that helps store data in the form of a string format. Read more about AsyncStorage here.
However, AsyncStorage is not specifically designed to store sensitive data securely. We can employ an additional security measure, such as strong encryption, to store sensitive data securely using AsyncStorage. We can encrypt our sensitive data before storing it, which will help protect it from being accessed by unauthorized individuals.
The react-native-crypto-js library
One way to encrypt data before storing it using AsyncStorage is to use the react-native-crypto-js library. This is a JavaScript library that provides various cryptographic algorithms, including AES encryption. Moreover, react-native-crypto-js is cross-platform compatible, i.e., it can be used to encrypt data on both Android and iOS operating systems.
To encrypt data using the react-native-crypto-js library, we have to import CryptoJS from react-native-crypto-js.
import CryptoJS from 'react-native-crypto-js';
Encryption key
In order to encrypt sensitive data, we first have to generate a strong encryption key. We can use the CryptoJS.lib.WordArray.random() method to do so. This method generates a random CryptoJS to represent an array of 32-bit words.toString() method.
The code snippet below shows how to generate a strong encryption key of 32 bytes (256 bits) using the CryptoJS.lib.WordArray.random method.
CryptoJS.lib.WordArray.random(256 / 8).toString();
Encrypting data
Once the encryption key has been generated, we can use the CryptoJS.AES.encrypt() method to encrypt data. This method takes in the following two parameters:
-
value: This is the data that needs to be encrypted. -
encryptionKey: This is the encryption key using which we encrypt thevalue.
Note: The
CryptoJS.AES.encryptmethod encrypts data in the form of bytes. We can convert it to a string using thetoString()method.
The code snippet below shows how to encrypt data using the CryptoJS.AES.encrypt method.
CryptoJS.AES.encrypt(value, encryptionKey).toString();
Decrypting data
The CryptoJS.AES.decrypt() method can be used to decrypt the encrypted data. This method takes in the following two parameters:
-
encryptedValue: This is the encrypted data that needs to be decrypted. -
encryptionKey: This is the encryption key using which we encrypted the data.
Note: The
CryptoJS.AES.decryptmethod decrypts data in the form of bytes. We can convert it to a string using thetoString()method. Furthermore, we also have to convert the decrypted data to a readable encoding scheme usingCryptoJS.enc.Utf8.
The code snippet below shows how to decrypt data using the CryptoJS.AES.decrypt method.
CryptoJS.AES.decrypt(encryptedValue, encryptionKey).toString(CryptoJS.enc.Utf8)
Code example
Let’s look at an example where we utilize AsyncStorage and the react-native-crypto-js library to store sensitive data in a React Native app.
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.reactnativelearning;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
/**
* Class responsible of loading Flipper inside your React Native application. This is the debug
* flavor of it. Here you can add your own plugins and customize the Flipper setup.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}
Code explanation
-
Line 1: We import
Reactand theuseStateHook from thereactlibrary. -
Lines 2–9: We import the required components from the
react-nativelibrary. -
Line 10: We import
AsyncStoragefrom@react-native-async-storage/async-storage. -
Line 11: We import
CryptoJSfromreact-native-crypto-js. -
Line 13: We use the
CryptoJS.lib.WordArray.randommethod to generate a strong encryption key of 32 bytes (256 bits). -
Line 17: We use the
useStateHook to initialize the state variablekey. We set the initial state as an empty string. -
Line 18: We use the
useStateHook to initialize the state variablevalue. We set the initial state as an empty string. -
Line 20: We define the
encryptfunction. Inside the function, we use theCryptoJS.AES.encryptmethod to encryptvalueusing theencryptionKey. -
Line 22: We define the
decryptfunction. Inside the function, we use theCryptoJS.AES.decryptmethod to decryptencryptedValueusing theencryptionKey. -
Lines 24–40: We define the function
storeDatato store our sensitive data. If the values forkeyandvalueare empty, an error will be shown using theAlertbox, and the function will return. Otherwise, we use theencryptfunction to encrypt the data. Once encrypted, we use thesetItemmethod ofAsyncStorageto store data according to the values ofkeyandvalueprovided. Once successfully stored, a success message is shown using theAlertbox. If there is any error while storing data, an error will be shown using theAlertbox. Relevant error details are then logged using theconsole.logmethod. -
Lines 42–60: We define the function
fetchDatato fetch our sensitive data. If the value forkeyis empty, an error will be shown using theAlertbox, and the function will return. Otherwise, we use thegetItemmethod ofAsyncStorageto fetch data according to the value of thekeyprovided. If the value returned by thegetItemmethod is notnull, we use thedecryptfunction to decrypt the fetched data. Once decrypted, we use theAlertbox to display the decrypted value. If the value returned by thegetItemmethod isnull, we use theAlertbox to show an error message. Furthermore, an error will be shown using theAlertbox if there’s any error while fetching data. Relevant error details are then logged using theconsole.logmethod. -
Lines 63–82: We write the logic for the user interface of the React Native application.
-
Lines 86–106: We define the styles for our user interface.
Note: We can download and execute the code example above on our local system or machine. Follow the official React Native documentation to set up the development environment. The code example above is for the Android operating system.
Free Resources