When developing iOS or Android apps, there’s a need to store some data locally, such as basic fetch data, usernames, and passwords. However, using standard storage methods like shared preferences on Android, Core Data and NSUserDefaults on iOS, or Async Storage in React Native may pose security risks. These traditional storage methods lack encryption, making the stored data susceptible to potential breaches, leading to unauthorized access or compromise on devices.
In this blog post, we will delve into techniques for secure local data storage, with a specific focus on Android and React Native.
Local Data Storage Techniques
Android Shared Preference
If you have a modest set of key-value pairs to store, consider leveraging the SharedPreferences APIs. These APIs allow you to create a SharedPreferences object, which points to a file holding key-value pairs and offers straightforward methods for reading and writing them. The framework manages each SharedPreferences file, allowing you to designate it as either private or shared.
1
2
3
4
5
SharedPreferences pref = getApplicationContext().getSharedPreferences("MyPref", 0);
Editor editor = pref.edit();
editor.putString("password", "s3cr3t"); // Storing string
editor.commit(); // Commit changes
This code snippet writes key-value pairs to an XML file located at /data/data/
Now, check the shared preference file, stored data is unencrypted xml data:
1
2
3
4
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="key_name">s3cr3t</string>
</map>
iOS NSUserDefaults
NSUserDefaults
is a class in the iOS (and macOS) development framework that provides a simple interface for persistently storing small amounts of data. It’s commonly used for storing user preferences, settings, and other configuration information.
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// Saving various data types
[prefs setObject:@"IT0809" forKey:@"AccessNo"];
[prefs setInteger:42 forKey:@"RollNo"];
[prefs setDouble:9.99 forKey:@"DoubleKey"];
[prefs setFloat:1.7565671 forKey:@"FloatKey"];
[prefs synchronize];
Data is stored in a .plist file in the ~/Library/Preferences/… folder.
React Native AsyncStorage
AsyncStorage is an asynchronous, persistent, key-value storage system. For Android, data is stored in SQLite, while for iOS, small values are serialized in a common manifest.json
file, and larger values are stored in dedicated files.
1
2
3
4
5
6
7
const storeData = async (value) => {
try {
await AsyncStorage.setItem('my-key', value);
} catch (e) {
// saving error
}
};
Securing Local Storage
Android Keystore
The Android Keystore system allows for the secure storage of cryptographic keys, with the keys stored in an environment inaccessible to the application. EncryptedSharedPreferences
is a function that transparently encrypts and decrypts using the keychain master key when reading or writing shared preferences.
MasterKey masterKey = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build();
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(context, "secret_shared_prefs", masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
iOS Keychain
The keychain services API provides a mechanism to store small encrypted user data. It allows secure storage of sensitive information like passwords.
React Native Security Libraries
Various React Native libraries implement secure storage for both iOS and Android:
- expo-secure-store
- react-native-encrypted-storage: Uses Keychain on iOS and EncryptedSharedPreferences on Android.
- react-native-keychain
- react-native-sensitive-info: Secure for iOS, with an alternative branch using Android Keystore.
Example using expo-secure-store to save a username and password:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import { StatusBar } from 'expo-status-bar';
import { Button, StyleSheet, Text, TextInput, View } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import * as VersionCheck from 'react-native-version-check-expo'
import { useState } from 'react';
export default function App() {
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const isShowPassword = true
async function onRemember () {
try {
await SecureStore.setItemAsync("username", username)
await SecureStore.setItemAsync("password", password)
alert("Saved")
} catch (e) {
console.log(e)
}
}
async function onLoadRemember () {
try {
const username = await SecureStore.getItemAsync('username')
const password = await SecureStore.getItemAsync('password')
setUsername(username)
setPassword(password)
alert("Loaded")
} catch (e) {
console.log(e)
}
}
async function onReset() {
setUsername("")
setPassword("")
}
return (
<View style={styles.container}>
<Text style=>Login into app</Text>
<TextInput onChangeText={setUsername} style={styles.input} placeholder='Username' value={username} />
<TextInput onChangeText={setPassword} style={styles.input} placeholder='Password' value={password} secureTextEntry={!isShowPassword} />
<Button onPress={onRemember} color={'#000000'} title='Remember'/>
<Text> </Text>
<Button onPress={onLoadRemember} color={'#000000'} title='Load Remember'/>
<Text> </Text>
<Button onPress={onReset} color={'#000000'} title='Reset'/>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
input: {
height: 40,
margin: 8,
borderWidth: 1,
padding: 10,
width: 200
},
});
Enter username, password and press remember. Then, check the shared preference file:
1
2
3
4
5
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="password">{"ct":"mJr\/weUfpWSrK8yy8K31sGjeBDZlX7gIM5DRQjLRTH6f2A==","iv":"U3oKZi4ic+wpoQ6u","tlen":128,"scheme":"aes"}</string>
<string name="username">{"ct":"PvfdYhUcY3mNAC3XTe6TtKX3S3K4BaMF","iv":"I2SKVAlsQhTjF5zV","tlen":128,"scheme":"aes"}</string>
</map>
By implementing secure storage solutions, developers can ensure the protection of sensitive local data against potential security threats and unauthorized access.