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.
react-native-crypto-js
libraryOne 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';
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();
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 the value
.
Note: The
CryptoJS.AES.encrypt
method 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();
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.decrypt
method 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)
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()); } } } }
Line 1: We import React
and the useState
Hook from the react
library.
Lines 2–9: We import the required components from the react-native
library.
Line 10: We import AsyncStorage
from @react-native-async-storage/async-storage
.
Line 11: We import CryptoJS
from react-native-crypto-js
.
Line 13: We use the CryptoJS.lib.WordArray.random
method to generate a strong encryption key of 32 bytes (256 bits).
Line 17: We use the useState
Hook to initialize the state variable key
. We set the initial state as an empty string.
Line 18: We use the useState
Hook to initialize the state variable value
. We set the initial state as an empty string.
Line 20: We define the encrypt
function. Inside the function, we use the CryptoJS.AES.encrypt
method to encrypt value
using the encryptionKey
.
Line 22: We define the decrypt
function. Inside the function, we use the CryptoJS.AES.decrypt
method to decrypt encryptedValue
using the encryptionKey
.
Lines 24–40: We define the function storeData
to store our sensitive data. If the values for key
and value
are empty, an error will be shown using the Alert
box, and the function will return. Otherwise, we use the encrypt
function to encrypt the data. Once encrypted, we use the setItem
method of AsyncStorage
to store data according to the values of key
and value
provided. Once successfully stored, a success message is shown using the Alert
box. If there is any error while storing data, an error will be shown using the Alert
box. Relevant error details are then logged using the console.log
method.
Lines 42–60: We define the function fetchData
to fetch our sensitive data. If the value for key
is empty, an error will be shown using the Alert
box, and the function will return. Otherwise, we use the getItem
method of AsyncStorage
to fetch data according to the value of the key
provided. If the value returned by the getItem
method is not null
, we use the decrypt
function to decrypt the fetched data. Once decrypted, we use the Alert
box to display the decrypted value. If the value returned by the getItem
method is null
, we use the Alert
box to show an error message. Furthermore, an error will be shown using the Alert
box if there’s any error while fetching data. Relevant error details are then logged using the console.log
method.
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.