├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── RNSecureStorage.podspec ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── taluttasgiran │ └── rnsecurestorage │ ├── PreferencesStorage.java │ ├── RNSecureStorageModule.java │ ├── RNSecureStoragePackage.java │ ├── SecurityLevel.java │ └── secureStorage │ └── SecureStorage.java ├── index.js ├── ios ├── RNSecureStorage-Bridging-Header.h ├── RNSecureStorage.m ├── RNSecureStorage.swift ├── RNSecureStorage.xcodeproj │ └── project.pbxproj ├── RNSecureStorage.xcworkspace │ └── contents.xcworkspacedata └── RNSecureStorageHelper.swift ├── package.json ├── rn-secure-storage.d.ts └── sample ├── .bundle ├── bin │ ├── bundle │ ├── fuzzy_match │ ├── httpclient │ ├── pod │ ├── sandbox-pod │ └── xcodeproj └── config ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .watchmanconfig ├── App.tsx ├── Gemfile ├── Gemfile.lock ├── README.md ├── __tests__ └── App.test.tsx ├── android ├── app │ ├── build.gradle │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── sample │ │ │ ├── MainActivity.kt │ │ │ └── MainApplication.kt │ │ └── res │ │ ├── drawable │ │ └── rn_edit_text_material.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── app.json ├── babel.config.js ├── index.js ├── ios ├── .xcode.env ├── Podfile ├── Podfile.lock ├── sample.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── sample.xcscheme ├── sample.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── sample │ ├── AppDelegate.h │ ├── AppDelegate.mm │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ ├── LaunchScreen.storyboard │ └── main.m └── sampleTests │ ├── Info.plist │ └── sampleTests.m ├── jest.config.js ├── metro.config.js ├── package.json ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: talut 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.buymeacoffee.com/talut'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX 3 | # 4 | .DS_Store 5 | 6 | # node.js 7 | # 8 | node_modules/ 9 | npm-debug.log 10 | yarn-error.log 11 | 12 | 13 | # Xcode 14 | # 15 | build/ 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata 25 | *.xccheckout 26 | *.moved-aside 27 | DerivedData 28 | *.hmap 29 | *.ipa 30 | *.xcuserstate 31 | project.xcworkspace 32 | 33 | 34 | # Android/IntelliJ 35 | # 36 | build/ 37 | .idea 38 | .gradle 39 | local.properties 40 | *.iml 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | ._* 3 | .DS_Store 4 | .git 5 | .hg 6 | .npmrc 7 | .lock-wscript 8 | .svn 9 | .wafpickle-* 10 | config.gypi 11 | CVS 12 | npm-debug.log 13 | example 14 | sample 15 | .idea 16 | 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Talut TASGIRAN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RNSecureStorage 2 | 3 | [![npm version](https://badge.fury.io/js/rn-secure-storage.svg)](https://badge.fury.io/js/rn-secure-storage) 4 | [![npm downloads](https://img.shields.io/npm/dm/rn-secure-storage.svg?maxAge=2592000)](https://img.shields.io/npm/dm/rn-secure-storage.svg?maxAge=2592000) 5 | ![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg) 6 | 7 | Secure Storage for React Native (Android & iOS) - Keychain & Keystore 8 | 9 | #### If you like this library, please consider supporting my work by buying me a coffee 10 | 11 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://buymeacoff.ee/talut) 12 | 13 | ### Thanks for using this library 14 | 15 | Please read my disclaimer about maintaining this library [here](#disclaimer) 16 | 17 | For Android [I need your reviews](#i-need-your-reviews) 18 | 19 | ## Getting Started 20 | 21 | **With NPM** 22 | 23 | ``` 24 | npm install --save rn-secure-storage 25 | ``` 26 | 27 | **With YARN** 28 | 29 | ``` 30 | yarn add rn-secure-storage 31 | ``` 32 | 33 | ### What's changed in v3.0.1 34 | - Return type for multiGet changed. https://github.com/talut/rn-secure-storage/pull/79 35 | 36 | ### What's changed in v3.0.0 37 | 38 | - Rewritten Android module with enhanced security features. 39 | - Android minSdkVersion is now 23 (Android 6.0 Marshmallow) 40 | - iOS module redeveloped using Swift and updated APIs. 41 | - Comprehensive renaming and expansion of APIs. 42 | - Modifications to the return types of some APIs. 43 | - Added `clear` for comprehensive data clearance. 44 | - Introduced `getAllKeys` for retrieving all stored keys. 45 | - Implemented `multiSet` for setting multiple values simultaneously. 46 | - New `multiGet` feature for fetching multiple values at once. 47 | - `multiRemove` added for bulk deletion of items. 48 | - `getSupportedBiometryType` introduced for iOS (supports biometric authentication types). 49 | 50 | ### API 51 | 52 | - [setItem](#setitem) 53 | - [getItem](#getitem) 54 | - [removeItem](#removeitem) 55 | - [exists](#exists) 56 | - [getAllKeys](#getallkeys) 57 | - [clear](#clear) 58 | - [multiSet](#multiset) 59 | - [multiGet](#multiget) 60 | - [multiRemove](#multiremove) 61 | 62 | ### setItem 63 | 64 | To set the item, you need to pass the key and value as parameters. You can also pass the options as a third parameter. If the key exists, 65 | the value will be updated, otherwise, it will be created. You can use the `exists` method to check if the key exists. You can also pass 66 | the `accessible` option to set the accessibility of the keychain item (only for IOS). 67 | 68 | ```js 69 | import RNSecureStorage, {ACCESSIBLE} from 'rn-secure-storage'; 70 | 71 | RNSecureStorage.setItem("key", "value", {accessible: ACCESSIBLE.WHEN_UNLOCKED}).then((res) => { 72 | console.log(res); 73 | }).catch((err) => { 74 | console.log(err); 75 | }); 76 | ``` 77 | 78 | ### getItem 79 | 80 | To get the item, you need to pass the key as a parameter. If the key exists, the value will be returned, otherwise, it will return an error. 81 | 82 | ```js 83 | RNSecureStorage.getItem("key").then((res) => { 84 | console.log(res); 85 | }).catch((err) => { 86 | console.log(err); 87 | }); 88 | ``` 89 | 90 | ### removeItem 91 | 92 | To remove the item, you need to pass the key as a parameter. If the key exists, the value will be removed, otherwise, it will return an 93 | error. 94 | 95 | ```js 96 | RNSecureStorage.removeItem("key").then((res) => { 97 | console.log(res); 98 | }).catch((err) => { 99 | console.log(err); 100 | }); 101 | ``` 102 | 103 | ### exists 104 | 105 | To check if the item exists, you need to pass the key as a parameter. If the key exists it will return `true`, otherwise, it will 106 | return `false`. Mostly you don't need to use this method because `getItem` will return an error if the key doesn't exist. But for 107 | performance you can just check if the key exists before calling `getItem`. 108 | 109 | ```js 110 | RNSecureStorage.exist("key").then((res) => { 111 | console.log(res ? "exists" : "does not exist"); 112 | }).catch((err) => { 113 | console.log(err); 114 | }); 115 | ``` 116 | 117 | ### getAllKeys 118 | 119 | To get all keys, you need to call `getAllKeys` method. It will return an array of keys. 120 | 121 | ```js 122 | RNSecureStorage.getAllKeys().then((res) => { 123 | console.log(res); 124 | }).catch((err) => { 125 | console.log(err); 126 | }); 127 | ``` 128 | 129 | ### clear 130 | 131 | To clear all data, you need to call `clear` method. It will return `true` if the operation is successful. Otherwise, it will return 132 | remaining keys. 133 | 134 | ```js 135 | RNSecureStorage.clear().then((res) => { 136 | console.log(res); 137 | }).catch((err) => { 138 | console.log(err); 139 | }); 140 | ``` 141 | 142 | ### multiSet 143 | 144 | Multi set is a new feature that allows you to set multiple values simultaneously. You need to pass an object as the first parameter 145 | 146 | ```js 147 | import RNSecureStorage, {ACCESSIBLE} from 'rn-secure-storage'; 148 | 149 | const items = {"key_1": "multi key 1", "key_2": "multi key 2"}; 150 | RNSecureStorage.multiSet(items, {accessible: ACCESSIBLE.WHEN_UNLOCKED}).then((res) => { 151 | console.log(res); 152 | }).catch((err) => { 153 | console.log(err); 154 | }); 155 | ``` 156 | 157 | ### multiGet 158 | 159 | With multi get you can fetch multiple values at once. You need to pass an array of keys as the first parameter. 160 | 161 | ```js 162 | RNSecureStorage.multiGet(["key_1", "key_2"]).then((res) => { 163 | console.log(res); 164 | }).catch((err) => { 165 | console.log(err); 166 | }); 167 | ``` 168 | 169 | ### multiRemove 170 | 171 | With multi remove you can delete multiple values at once. You need to pass an array of keys as the first parameter. If your keys are 172 | removed/not found, it will return an array of keys that are not removed/found. 173 | 174 | ```js 175 | RNSecureStorage.multiRemove(["key_1", "key_2"]).then((res) => { 176 | console.log(res); 177 | }).catch((err) => { 178 | console.log(err); 179 | }); 180 | ``` 181 | 182 | ## Options 183 | 184 | | Key | Platform | Description | Default | 185 | |------------------|----------|--------------------------------------------------------------------------------------------------|---------------------------------------| 186 | | **`accessible`** | iOS only | This indicates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. | *`Keychain.ACCESSIBLE.WHEN_UNLOCKED`* | 187 | 188 | ### `Keychain.ACCESSIBLE` enum 189 | 190 | | Key | Description | 191 | |-------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 192 | | **`WHEN_UNLOCKED`** | The data in the keychain item can be accessed only while the device is unlocked by the user. | 193 | | **`AFTER_FIRST_UNLOCK`** | The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. | 194 | | **`ALWAYS`** | The data in the keychain item can always be accessed regardless of whether the device is locked. | 195 | | **`WHEN_PASSCODE_SET_THIS_DEVICE_ONLY`** | The data in the keychain can only be accessed when the device is unlocked. Only available if a passcode is set on the device. Items with this attribute never migrate to a new device. | 196 | | **`WHEN_UNLOCKED_THIS_DEVICE_ONLY`** | The data in the keychain item can be accessed only while the device is unlocked by the user. Items with this attribute do not migrate to a new device. | 197 | | **`AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`** | The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. Items with this attribute never migrate to a new device. | 198 | | **`ALWAYS_THIS_DEVICE_ONLY`** | The data in the keychain item can always be accessed regardless of whether the device is locked. Items with this attribute never migrate to a new device. | 199 | 200 | #### You can also check out sample project for more details 201 | 202 | ## License 203 | 204 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 205 | 206 | ### I need your reviews 207 | 208 | I have rewritten the Android module with enhanced security features. I need your reviews. Please test the new version and let me know your 209 | thoughts. I will be happy to hear your suggestions and comments. I'm planning to release the new version to handle biometric authentication 210 | on Android. 211 | 212 | ### Connect with me 213 | 214 |

215 | taluttasgiran 216 | taluttasgiran 217 | talut 218 | 3366361 219 | @taluttasgiran 220 |

221 | 222 | ### Disclaimer 223 | 224 | As an open-source enthusiast and developer, I originally created this library for professional use. However, with recent changes in my 225 | career, my focus has shifted away from React Native, limiting my involvement to personal projects. Consequently, my time to address issues 226 | and review pull requests for this library has become restricted. I remain committed to maintaining and improving this library, but my 227 | response times might be longer. I greatly appreciate your patience and understanding. 228 | 229 | The beauty of open-source is in collaboration. If you find this library useful and would like to contribute, whether through code, 230 | addressing issues, or even buying a coffee to show support, it would be immensely appreciated. Together, we can ensure the continued 231 | development and enhancement of this library. 232 | 233 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/talut) 234 | -------------------------------------------------------------------------------- /RNSecureStorage.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "RNSecureStorage" 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | 11 | s.authors = package['author'] 12 | s.homepage = package['homepage'] 13 | s.platforms = { :ios => "10.0", :tvos => "9.2", :osx => "10.14" } 14 | 15 | s.source = { :git => "https://github.com/talut/rn-secure-storage.git", :tag => "v#{s.version}" } 16 | s.source_files = "ios/**/*.{h,m,swift}" 17 | s.dependency 'React' 18 | end 19 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | // The Android Gradle plugin is only required when opening the android folder stand-alone. 3 | // This avoids unnecessary downloads and potential conflicts when the library is included as a 4 | // module dependency in an application project. 5 | if (project == rootProject) { 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath("com.android.tools.build:gradle") 12 | } 13 | } 14 | } 15 | 16 | apply plugin: 'com.android.library' 17 | 18 | def safeExtGet(prop, fallback) { 19 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 20 | } 21 | 22 | android { 23 | compileSdkVersion safeExtGet('compileSdkVersion', 34) 24 | buildToolsVersion safeExtGet('buildToolsVersion', '34.0.0') 25 | 26 | defaultConfig { 27 | minSdkVersion safeExtGet('minSdkVersion', 23) 28 | targetSdkVersion safeExtGet('targetSdkVersion', 34) 29 | versionCode 4 30 | versionName "3.0.0" 31 | } 32 | 33 | lintOptions { 34 | abortOnError false 35 | } 36 | 37 | compileOptions { 38 | sourceCompatibility JavaVersion.VERSION_1_8 39 | targetCompatibility JavaVersion.VERSION_1_8 40 | } 41 | } 42 | 43 | repositories { 44 | mavenCentral() 45 | } 46 | 47 | dependencies { 48 | implementation "com.facebook.react:react-native:+" 49 | implementation 'androidx.appcompat:appcompat:1.6.1' 50 | } 51 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /android/src/main/java/com/taluttasgiran/rnsecurestorage/PreferencesStorage.java: -------------------------------------------------------------------------------- 1 | package com.taluttasgiran.rnsecurestorage; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.util.Base64; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | 12 | import org.json.JSONArray; 13 | 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Locale; 18 | 19 | /** 20 | * Shared preferences storage. 21 | *

22 | * This class is used to store encrypted values in shared preferences. 23 | * It is used to store the encryption key and the initialization vector. 24 | * The key and the initialization vector are used to encrypt and decrypt the values. 25 | *

26 | */ 27 | public class PreferencesStorage { 28 | public static final String RN_SECURE_STORAGE = "RN_SECURE_STORAGE"; 29 | 30 | @NonNull 31 | private final SharedPreferences prefs; 32 | 33 | public PreferencesStorage(@NonNull final ReactApplicationContext reactContext) { 34 | String prefsName = reactContext.getPackageName() + "." + RN_SECURE_STORAGE; 35 | String prefNameBase64 = Base64.encodeToString(prefsName.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT); 36 | String fileName = prefNameBase64.toLowerCase(Locale.ROOT).replaceAll("[^a-z]", ""); 37 | this.prefs = reactContext.getSharedPreferences(fileName, Context.MODE_PRIVATE); 38 | } 39 | 40 | 41 | @Nullable 42 | public String getEncryptedEntry(@NonNull final String key) { 43 | return this.prefs.getString(key, null); 44 | } 45 | 46 | public boolean removeEntry(@NonNull final String key) { 47 | if (this.prefs.getString(key, null) != null) { 48 | prefs.edit().remove(key).apply(); 49 | return true; 50 | } else { 51 | return false; 52 | } 53 | } 54 | 55 | public boolean clear() { 56 | return this.prefs.edit().clear().commit(); 57 | } 58 | 59 | public boolean exist(@NonNull final String key) { 60 | return this.prefs.contains(key); 61 | } 62 | 63 | public void storeEncryptedEntry(@NonNull final String key, @NonNull final String encryptedValue) { 64 | prefs.edit().putString(key, encryptedValue).apply(); 65 | } 66 | 67 | 68 | public JSONArray getAllStoredKeys() { 69 | List list = new ArrayList<>(this.prefs.getAll().keySet()); 70 | return new JSONArray(list); 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /android/src/main/java/com/taluttasgiran/rnsecurestorage/RNSecureStorageModule.java: -------------------------------------------------------------------------------- 1 | package com.taluttasgiran.rnsecurestorage; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.bridge.Promise; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 8 | import com.facebook.react.bridge.ReactMethod; 9 | import com.facebook.react.bridge.ReadableArray; 10 | import com.facebook.react.bridge.ReadableMap; 11 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 12 | import com.facebook.react.bridge.WritableArray; 13 | import com.facebook.react.bridge.WritableMap; 14 | import com.facebook.react.bridge.WritableNativeArray; 15 | import com.facebook.react.bridge.WritableNativeMap; 16 | import com.taluttasgiran.rnsecurestorage.secureStorage.SecureStorage; 17 | 18 | public class RNSecureStorageModule extends ReactContextBaseJavaModule { 19 | public static final String RN_SECURE_STORAGE = "RNSecureStorage"; 20 | 21 | /** 22 | * Known error codes. 23 | */ 24 | @interface Errors { 25 | String KEY_REQUIRED = "KEY_REQUIRED"; 26 | String NOT_FOUND = "NOT_FOUND"; 27 | String VALUE_REQUIRED = "VALUE_REQUIRED"; 28 | String UNKNOWN_ERROR = "UNKNOWN_ERROR"; 29 | } 30 | 31 | /** 32 | * Name-to-instance lookup map. 33 | */ 34 | private final SecureStorage secureStorage; 35 | /** 36 | * Shared preferences storage. 37 | */ 38 | private final PreferencesStorage prefsStorage; 39 | 40 | /** 41 | * Default constructor. 42 | */ 43 | public RNSecureStorageModule(@NonNull final ReactApplicationContext reactContext) { 44 | super(reactContext); 45 | prefsStorage = new PreferencesStorage(reactContext); 46 | try { 47 | secureStorage = new SecureStorage(); 48 | } catch (Exception e) { 49 | throw new RuntimeException(e); 50 | } 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | @Override 57 | @NonNull 58 | public String getName() { 59 | return RN_SECURE_STORAGE; 60 | } 61 | 62 | /** 63 | * Set a value. 64 | */ 65 | @ReactMethod 66 | protected void setItem(@NonNull final String key, @NonNull final String value, @NonNull final Promise promise) { 67 | try { 68 | String encryptedValue = secureStorage.encrypt(value); 69 | prefsStorage.storeEncryptedEntry(key, encryptedValue); 70 | promise.resolve("Stored successfully"); 71 | } catch (Exception e) { 72 | promise.reject("RNSecureStorage: An error occurred during key store", e); 73 | } 74 | } 75 | 76 | /** 77 | * Get a value from secure storage. 78 | */ 79 | @ReactMethod 80 | protected void getItem(@NonNull final String key, @NonNull final Promise promise) { 81 | try { 82 | String encryptedValue = prefsStorage.getEncryptedEntry(key); 83 | if (encryptedValue != null) { 84 | promise.resolve(secureStorage.decrypt(encryptedValue)); 85 | } else { 86 | promise.reject(Errors.NOT_FOUND, "RNSecureStorage: Value for " + key + " does not exist."); 87 | } 88 | } catch (Exception e) { 89 | promise.reject(e); 90 | } 91 | } 92 | 93 | /** 94 | * Checks if a key has been set. 95 | */ 96 | @ReactMethod 97 | public void exist(String key, Promise promise) { 98 | try { 99 | promise.resolve(this.prefsStorage.exist(key)); 100 | } catch (Exception e) { 101 | promise.reject(e); 102 | } 103 | } 104 | 105 | /** 106 | * Multiple key pair set for secure storage 107 | */ 108 | @ReactMethod 109 | public void multiSet(ReadableMap keyValuePairs, Promise promise) { 110 | if (keyValuePairs.toHashMap().size() > 0) { 111 | ReadableMapKeySetIterator iterator = keyValuePairs.keySetIterator(); 112 | while (iterator.hasNextKey()) { 113 | String key = iterator.nextKey(); 114 | String value = keyValuePairs.getString(key); 115 | if (value == null) { 116 | promise.reject(Errors.VALUE_REQUIRED, "RNSecureStorage: You must provide a value to store this key: " + key); 117 | return; 118 | } 119 | setItem(key, value, promise); 120 | } 121 | promise.resolve("Stored successfully"); 122 | } else { 123 | promise.reject(Errors.VALUE_REQUIRED, "RNSecureStorage: You must provide at least one key/value pair to store"); 124 | } 125 | } 126 | 127 | /** 128 | * Get multiple values from secure storage. 129 | */ 130 | @ReactMethod 131 | public void multiGet(ReadableArray keys, Promise promise) { 132 | WritableMap resultMap = new WritableNativeMap(); 133 | if (keys.size() > 0) { 134 | final int size = keys.size(); 135 | for (int i = 0; i < size; i++) { 136 | String key = keys.getString(i); 137 | String encryptedValue = prefsStorage.getEncryptedEntry(key); 138 | if (encryptedValue != null) { 139 | resultMap.putString(key, secureStorage.decrypt(encryptedValue)); 140 | } else { 141 | resultMap.putString(key, null); 142 | } 143 | } 144 | promise.resolve(resultMap); 145 | } else { 146 | promise.reject(Errors.KEY_REQUIRED, "RNSecureStorage: You must provide at least one key to get"); 147 | } 148 | } 149 | 150 | /** 151 | * Get all setted keys from secure storage. 152 | */ 153 | @ReactMethod 154 | public void getAllKeys(Promise promise) { 155 | try { 156 | promise.resolve(String.valueOf(this.prefsStorage.getAllStoredKeys())); 157 | } catch (Exception e) { 158 | promise.reject(Errors.NOT_FOUND, "RNSecureStorage: There are no stored keys."); 159 | } 160 | } 161 | 162 | /** 163 | * Remove a value from secure storage. 164 | */ 165 | @ReactMethod 166 | public void removeItem(String key, Promise promise) { 167 | try { 168 | if (this.prefsStorage.removeEntry(key)) { 169 | promise.resolve("Removed successfully"); 170 | } else { 171 | promise.reject(Errors.NOT_FOUND, "RNSecureStorage: Value for key " + key + " does not exist."); 172 | } 173 | } catch (Exception e) { 174 | promise.reject(e); 175 | } 176 | } 177 | 178 | /** 179 | * Remove values from secure storage (On error will return unremoved keys) 180 | */ 181 | @ReactMethod 182 | public void multiRemove(ReadableArray keys, Promise promise) { 183 | WritableArray unremovedKeys = new WritableNativeArray(); 184 | if (keys.size() > 0) { 185 | final int size = keys.size(); 186 | for (int i = 0; i < size; i++) { 187 | String key = keys.getString(i); 188 | if (!this.prefsStorage.removeEntry(key)) { 189 | unremovedKeys.pushString(key); 190 | } 191 | } 192 | if (unremovedKeys.size() > 0) { 193 | promise.reject(Errors.NOT_FOUND, "RNSecureStorage: Value for keys " + unremovedKeys + " does not exist."); 194 | } else { 195 | promise.resolve("Removed successfully"); 196 | } 197 | } else { 198 | promise.reject(Errors.KEY_REQUIRED, "RNSecureStorage: You must provide at least one key to remove"); 199 | } 200 | } 201 | 202 | /** 203 | * Removes whole RNSecureStorage data (On error will return unremoved keys) 204 | */ 205 | @ReactMethod 206 | public void clear(Promise promise) { 207 | try { 208 | if (this.prefsStorage.clear()) { 209 | if (this.prefsStorage.getAllStoredKeys().isNull(0)) { 210 | promise.resolve(true); 211 | } else { 212 | promise.resolve(String.valueOf(this.prefsStorage.getAllStoredKeys())); 213 | } 214 | } else { 215 | promise.reject(Errors.UNKNOWN_ERROR, "RNSecureStorage: An error occurred during key remove"); 216 | } 217 | } catch (Exception e) { 218 | promise.reject(e); 219 | } 220 | } 221 | 222 | } 223 | -------------------------------------------------------------------------------- /android/src/main/java/com/taluttasgiran/rnsecurestorage/RNSecureStoragePackage.java: -------------------------------------------------------------------------------- 1 | package com.taluttasgiran.rnsecurestorage; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.ViewManager; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class RNSecureStoragePackage implements ReactPackage { 11 | @Override 12 | public List createNativeModules(ReactApplicationContext reactContext) { 13 | return Collections.singletonList(new RNSecureStorageModule(reactContext)); 14 | } 15 | 16 | @Override 17 | public List createViewManagers(ReactApplicationContext reactContext) { 18 | return Collections.emptyList(); 19 | } 20 | } -------------------------------------------------------------------------------- /android/src/main/java/com/taluttasgiran/rnsecurestorage/SecurityLevel.java: -------------------------------------------------------------------------------- 1 | package com.taluttasgiran.rnsecurestorage; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | /** 6 | * Minimal required level of the security implementation. 7 | */ 8 | public enum SecurityLevel { 9 | /** 10 | * No security guarantees needed (default value); Credentials can be stored in FB Secure Storage 11 | */ 12 | ANY, 13 | /** 14 | * Requires for the key to be stored in the Android Keystore, separate from the encrypted data. 15 | */ 16 | SECURE_SOFTWARE, 17 | /** 18 | * Requires for the key to be stored on a secure hardware (Trusted Execution Environment or Secure Environment). 19 | */ 20 | SECURE_HARDWARE; 21 | 22 | /** 23 | * Get JavaScript friendly name. 24 | */ 25 | @NonNull 26 | public String jsName() { 27 | return String.format("SECURITY_LEVEL_%s", this.name()); 28 | } 29 | 30 | public boolean satisfiesSafetyThreshold(@NonNull final SecurityLevel threshold) { 31 | return this.compareTo(threshold) >= 0; 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /android/src/main/java/com/taluttasgiran/rnsecurestorage/secureStorage/SecureStorage.java: -------------------------------------------------------------------------------- 1 | package com.taluttasgiran.rnsecurestorage.secureStorage; 2 | 3 | import android.security.keystore.KeyGenParameterSpec; 4 | import android.security.keystore.KeyProperties; 5 | import android.util.Base64; 6 | 7 | import java.security.KeyStore; 8 | import java.security.SecureRandom; 9 | 10 | import javax.crypto.Cipher; 11 | import javax.crypto.KeyGenerator; 12 | import javax.crypto.SecretKey; 13 | import javax.crypto.spec.GCMParameterSpec; 14 | 15 | public class SecureStorage { 16 | 17 | private static final String KEY_ALIAS = "RNSecureStorage"; 18 | private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; 19 | private static final String TRANSFORMATION = "AES/GCM/NoPadding"; 20 | private static final int GCM_IV_LENGTH = 12; // Bytes 21 | private static final int GCM_TAG_LENGTH = 16; // Bytes 22 | 23 | private KeyStore keyStore; 24 | 25 | public SecureStorage() { 26 | initKeyStore(); 27 | } 28 | 29 | private void initKeyStore() throws RuntimeException { 30 | try { 31 | keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); 32 | keyStore.load(null); 33 | 34 | if (!keyStore.containsAlias(KEY_ALIAS)) { 35 | KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE); 36 | keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_ALIAS, 37 | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 38 | .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 39 | .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 40 | .setRandomizedEncryptionRequired(false) 41 | .build()); 42 | keyGenerator.generateKey(); 43 | } 44 | } catch (Exception e) { 45 | throw new RuntimeException(e); 46 | } 47 | } 48 | 49 | public String encrypt(String input) { 50 | try { 51 | SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null); 52 | Cipher cipher = Cipher.getInstance(TRANSFORMATION); 53 | byte[] iv = new byte[GCM_IV_LENGTH]; 54 | new SecureRandom().nextBytes(iv); 55 | cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(8 * GCM_TAG_LENGTH, iv)); 56 | byte[] encryptedBytes = cipher.doFinal(input.getBytes()); 57 | String encryptedBase64 = Base64.encodeToString(encryptedBytes, Base64.DEFAULT); 58 | String ivBase64 = Base64.encodeToString(iv, Base64.DEFAULT); 59 | return ivBase64 + ":" + encryptedBase64; 60 | } catch (Exception e) { 61 | // Handle exceptions 62 | return null; 63 | } 64 | } 65 | 66 | public String decrypt(String encrypted) { 67 | try { 68 | String[] parts = encrypted.split(":"); 69 | byte[] iv = Base64.decode(parts[0], Base64.DEFAULT); 70 | byte[] encryptedBytes = Base64.decode(parts[1], Base64.DEFAULT); 71 | 72 | SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null); 73 | Cipher cipher = Cipher.getInstance(TRANSFORMATION); 74 | cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(8 * GCM_TAG_LENGTH, iv)); 75 | byte[] decryptedBytes = cipher.doFinal(encryptedBytes); 76 | return new String(decryptedBytes); 77 | } catch (Exception e) { 78 | // Handle exceptions 79 | return null; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {NativeModules, Platform} from "react-native"; 2 | 3 | export const ACCESSIBLE = { 4 | WHEN_UNLOCKED: "AccessibleWhenUnlocked", 5 | AFTER_FIRST_UNLOCK: "AccessibleAfterFirstUnlock", 6 | ALWAYS: "AccessibleAlways", 7 | WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: "AccessibleWhenPasscodeSetThisDeviceOnly", 8 | WHEN_UNLOCKED_THIS_DEVICE_ONLY: "AccessibleWhenUnlockedThisDeviceOnly", 9 | AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: "AccessibleAfterFirstUnlockThisDeviceOnly", 10 | ALWAYS_THIS_DEVICE_ONLY: "AccessibleAlwaysThisDeviceOnly", 11 | }; 12 | const {RNSecureStorage} = NativeModules; 13 | 14 | const set = RNSecureStorage.setItem; 15 | RNSecureStorage.setItem = (key, value, options = {}) => { 16 | if (Platform.OS === "android") { 17 | return set(key, value); 18 | } 19 | return set(key, value, options); 20 | }; 21 | 22 | const multiSet = RNSecureStorage.multiSet; 23 | RNSecureStorage.multiSet = (keyValuePairs, options = {}) => { 24 | if (Platform.OS === "android") { 25 | return multiSet(keyValuePairs); 26 | } 27 | return multiSet(keyValuePairs, options); 28 | }; 29 | 30 | const getAllKeys = RNSecureStorage.getAllKeys; 31 | RNSecureStorage.getAllKeys = () => { 32 | if (Platform.OS === "android") { 33 | return new Promise((resolve, reject) => { 34 | getAllKeys() 35 | .then((keys) => keys ? resolve(JSON.parse(keys)) : resolve(null)) 36 | .catch((error) => reject(error)); 37 | }); 38 | } 39 | return getAllKeys(); 40 | }; 41 | 42 | export default RNSecureStorage; 43 | -------------------------------------------------------------------------------- /ios/RNSecureStorage-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import 2 | -------------------------------------------------------------------------------- /ios/RNSecureStorage.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface RCT_EXTERN_MODULE(RNSecureStorage, NSObject) 5 | 6 | /** 7 | * Set a value from secure storage. 8 | */ 9 | RCT_EXTERN_METHOD(setItem:(NSString *)key value:(NSString *)value options:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 10 | 11 | /** 12 | * Get a value from secure storage. 13 | */ 14 | RCT_EXTERN_METHOD(getItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 15 | 16 | /** 17 | * Checks if a key has been set. 18 | */ 19 | RCT_EXTERN_METHOD(exist:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 20 | 21 | /** 22 | * Get all stored keys from secure storage. 23 | */ 24 | RCT_EXTERN_METHOD(getAllKeys:(RCTPromiseResolveBlock *)resolver rejecter:(RCTPromiseRejectBlock)reject) 25 | 26 | /** 27 | * Multiple key pair set for secure storage 28 | */ 29 | RCT_EXTERN_METHOD(multiSet:(NSDictionary *)keyValuePairs options:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)reject) 30 | 31 | /** 32 | * Get multiple values from secure storage. 33 | */ 34 | RCT_EXTERN_METHOD(multiGet:(NSArray *)keys resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)reject) 35 | 36 | /** 37 | * Remove a value from secure storage. 38 | */ 39 | RCT_EXTERN_METHOD(removeItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 40 | 41 | /** 42 | * Remove values from secure storage 43 | */ 44 | RCT_EXTERN_METHOD(multiRemove:(NSArray *)keys resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)reject) 45 | 46 | /** 47 | Remove all stored keys. (On error will return unremoved keys) 48 | */ 49 | RCT_EXTERN_METHOD(clear:(RCTPromiseResolveBlock *)resolver rejecter:(RCTPromiseRejectBlock)reject) 50 | 51 | /** 52 | Get supported biometry type 53 | */ 54 | RCT_EXTERN_METHOD(getSupportedBiometryType: (RCTPromiseResolveBlock *)resolver rejecter:(RCTPromiseRejectBlock *)reject) 55 | 56 | 57 | + (BOOL)requiresMainQueueSetup 58 | { 59 | return YES; 60 | } 61 | @end 62 | -------------------------------------------------------------------------------- /ios/RNSecureStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Security 3 | import LocalAuthentication 4 | 5 | public enum RNSecureErrors: Error { 6 | case KEY_NOT_STORED 7 | case KEY_REQUIRED 8 | case KEY_NOT_REMOVED 9 | case NOT_FOUND 10 | case UNKNOWN_ERROR 11 | } 12 | 13 | @objc(RNSecureStorage) 14 | class RNSecureStorage: NSObject { 15 | let helper = RNSecureStorageHelper.init() 16 | 17 | 18 | @objc(setItem:value:options:resolver:rejecter:) 19 | func setItem(_ key:String, value:String, options:[String:Any], resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){ 20 | let accessible = options["accessible"] as! String 21 | let status = helper.createKeychainValue(key: key, value: value, accessible: accessible) 22 | if status { 23 | resolver("Key stored successfully") 24 | }else{ 25 | rejecter("KEY_NOT_STORED","RNSecureStorage: An error occurred during key storage", RNSecureErrors.KEY_NOT_STORED) 26 | } 27 | } 28 | 29 | @objc(getItem:resolver:rejecter:) 30 | func getItem(_ key:String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){ 31 | let val = helper.getKeychainValue(key: key) 32 | if val == nil { 33 | rejecter("NOT_FOUND","RNSecureStorage: Key does not present", RNSecureErrors.NOT_FOUND) 34 | } else { 35 | let result = String(data: val!, encoding: .utf8) 36 | resolver(result) 37 | } 38 | } 39 | 40 | @objc(exist:resolver:rejecter:) 41 | func exist(_ key:String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){ 42 | let val = helper.keychainValueExist(key: key) 43 | resolver(val) 44 | } 45 | 46 | @objc(getAllKeys:rejecter:) 47 | func getAllKeys(_ resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){ 48 | 49 | let keys = helper.getAllKeychainKeys() 50 | 51 | if keys.count > 0 { 52 | resolver(keys) 53 | }else{ 54 | rejecter("NOT_FOUND","RNSecureStorage: There are no stored keys.", RNSecureErrors.NOT_FOUND) 55 | } 56 | } 57 | 58 | @objc(multiSet:options:resolver:rejecter:) 59 | func multiSet(_ keyValuePairs:[String:String], options:[String:Any], resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){ 60 | let accessible = options["accessible"] as! String 61 | let status = helper.multiSetKeychainValues(keyValuePairs: keyValuePairs, accessible: accessible) 62 | if status { 63 | resolver("Key stored successfully") 64 | }else{ 65 | rejecter("KEY_NOT_STORED","RNSecureStorage: An error occurred during key storage", RNSecureErrors.KEY_NOT_STORED) 66 | } 67 | } 68 | 69 | /* 70 | Multi get values by keys array. 71 | */ 72 | @objc(multiGet:resolver:rejecter:) 73 | func multiGet(_ keys: [String], resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) { 74 | do { 75 | let vals = try helper.multiGetKeychainValues(keys: keys) 76 | resolver(vals) 77 | } catch RNSecureErrors.KEY_REQUIRED { 78 | rejecter("KEY_REQUIRED", "You must provide at least one key to get", RNSecureErrors.KEY_REQUIRED) 79 | } catch { 80 | rejecter("UNKNOWN_ERROR", "An unknown error occurred", error) 81 | } 82 | } 83 | 84 | @objc(removeItem:resolver:rejecter:) 85 | func removeItem(_ key:String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){ 86 | let status = helper.removeKeychainValue(key: key) 87 | if status { 88 | resolver("Key removed successfully") 89 | }else{ 90 | rejecter("KEY_NOT_REMOVED","RNSecureStorage: An error occurred during key remove", RNSecureErrors.KEY_NOT_REMOVED) 91 | } 92 | } 93 | 94 | /* 95 | Clear all given keys. (On error will return un-removed keys) 96 | */ 97 | @objc(multiRemove:resolver:rejecter:) 98 | func multiRemove(_ keys:[String], resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){ 99 | let unremovedKeys = helper.multiRemoveKeychainValue(keys: keys) 100 | if unremovedKeys.count>0 { 101 | resolver(unremovedKeys) 102 | }else{ 103 | resolver("Keys removed successfully") 104 | } 105 | } 106 | 107 | 108 | /* 109 | Clear all stored keys. (On error will return unremoved keys) 110 | */ 111 | @objc(clear:rejecter:) 112 | func clear(_ resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock){ 113 | let keys = helper.clearAllStoredValues() 114 | if keys.count > 0 { 115 | resolver(keys) 116 | }else{ 117 | resolver("All stored keys removed.") 118 | } 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /ios/RNSecureStorage.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B3E7B58A1CC2AC0600A0062D /* RNSecureStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNSecureStorage.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = "include/$(PRODUCT_NAME)"; 18 | dstSubfolderSpec = 16; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 0; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 134814201AA4EA6300B7C361 /* libRNSecureStorage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNSecureStorage.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | B3E7B5881CC2AC0600A0062D /* RNSecureStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSecureStorage.h; sourceTree = ""; }; 28 | B3E7B5891CC2AC0600A0062D /* RNSecureStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSecureStorage.m; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 134814211AA4EA7D00B7C361 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 134814201AA4EA6300B7C361 /* libRNSecureStorage.a */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 58B511D21A9E6C8500147676 = { 51 | isa = PBXGroup; 52 | children = ( 53 | B3E7B5881CC2AC0600A0062D /* RNSecureStorage.h */, 54 | B3E7B5891CC2AC0600A0062D /* RNSecureStorage.m */, 55 | 134814211AA4EA7D00B7C361 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | /* End PBXGroup section */ 60 | 61 | /* Begin PBXNativeTarget section */ 62 | 58B511DA1A9E6C8500147676 /* RNSecureStorage */ = { 63 | isa = PBXNativeTarget; 64 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNSecureStorage" */; 65 | buildPhases = ( 66 | 58B511D71A9E6C8500147676 /* Sources */, 67 | 58B511D81A9E6C8500147676 /* Frameworks */, 68 | 58B511D91A9E6C8500147676 /* CopyFiles */, 69 | ); 70 | buildRules = ( 71 | ); 72 | dependencies = ( 73 | ); 74 | name = RNSecureStorage; 75 | productName = RCTDataManager; 76 | productReference = 134814201AA4EA6300B7C361 /* libRNSecureStorage.a */; 77 | productType = "com.apple.product-type.library.static"; 78 | }; 79 | /* End PBXNativeTarget section */ 80 | 81 | /* Begin PBXProject section */ 82 | 58B511D31A9E6C8500147676 /* Project object */ = { 83 | isa = PBXProject; 84 | attributes = { 85 | LastUpgradeCheck = 0830; 86 | ORGANIZATIONNAME = Facebook; 87 | TargetAttributes = { 88 | 58B511DA1A9E6C8500147676 = { 89 | CreatedOnToolsVersion = 6.1.1; 90 | }; 91 | }; 92 | }; 93 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNSecureStorage" */; 94 | compatibilityVersion = "Xcode 3.2"; 95 | developmentRegion = English; 96 | hasScannedForEncodings = 0; 97 | knownRegions = ( 98 | en, 99 | ); 100 | mainGroup = 58B511D21A9E6C8500147676; 101 | productRefGroup = 58B511D21A9E6C8500147676; 102 | projectDirPath = ""; 103 | projectRoot = ""; 104 | targets = ( 105 | 58B511DA1A9E6C8500147676 /* RNSecureStorage */, 106 | ); 107 | }; 108 | /* End PBXProject section */ 109 | 110 | /* Begin PBXSourcesBuildPhase section */ 111 | 58B511D71A9E6C8500147676 /* Sources */ = { 112 | isa = PBXSourcesBuildPhase; 113 | buildActionMask = 2147483647; 114 | files = ( 115 | B3E7B58A1CC2AC0600A0062D /* RNSecureStorage.m in Sources */, 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | /* End PBXSourcesBuildPhase section */ 120 | 121 | /* Begin XCBuildConfiguration section */ 122 | 58B511ED1A9E6C8500147676 /* Debug */ = { 123 | isa = XCBuildConfiguration; 124 | buildSettings = { 125 | ALWAYS_SEARCH_USER_PATHS = NO; 126 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 127 | CLANG_CXX_LIBRARY = "libc++"; 128 | CLANG_ENABLE_MODULES = YES; 129 | CLANG_ENABLE_OBJC_ARC = YES; 130 | CLANG_WARN_BOOL_CONVERSION = YES; 131 | CLANG_WARN_CONSTANT_CONVERSION = YES; 132 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 133 | CLANG_WARN_EMPTY_BODY = YES; 134 | CLANG_WARN_ENUM_CONVERSION = YES; 135 | CLANG_WARN_INFINITE_RECURSION = YES; 136 | CLANG_WARN_INT_CONVERSION = YES; 137 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 138 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 139 | CLANG_WARN_UNREACHABLE_CODE = YES; 140 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 141 | COPY_PHASE_STRIP = NO; 142 | ENABLE_STRICT_OBJC_MSGSEND = YES; 143 | ENABLE_TESTABILITY = YES; 144 | GCC_C_LANGUAGE_STANDARD = gnu99; 145 | GCC_DYNAMIC_NO_PIC = NO; 146 | GCC_NO_COMMON_BLOCKS = YES; 147 | GCC_OPTIMIZATION_LEVEL = 0; 148 | GCC_PREPROCESSOR_DEFINITIONS = ( 149 | "DEBUG=1", 150 | "$(inherited)", 151 | ); 152 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 153 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 154 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 155 | GCC_WARN_UNDECLARED_SELECTOR = YES; 156 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 157 | GCC_WARN_UNUSED_FUNCTION = YES; 158 | GCC_WARN_UNUSED_VARIABLE = YES; 159 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 160 | MTL_ENABLE_DEBUG_INFO = YES; 161 | ONLY_ACTIVE_ARCH = YES; 162 | SDKROOT = iphoneos; 163 | }; 164 | name = Debug; 165 | }; 166 | 58B511EE1A9E6C8500147676 /* Release */ = { 167 | isa = XCBuildConfiguration; 168 | buildSettings = { 169 | ALWAYS_SEARCH_USER_PATHS = NO; 170 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 171 | CLANG_CXX_LIBRARY = "libc++"; 172 | CLANG_ENABLE_MODULES = YES; 173 | CLANG_ENABLE_OBJC_ARC = YES; 174 | CLANG_WARN_BOOL_CONVERSION = YES; 175 | CLANG_WARN_CONSTANT_CONVERSION = YES; 176 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 177 | CLANG_WARN_EMPTY_BODY = YES; 178 | CLANG_WARN_ENUM_CONVERSION = YES; 179 | CLANG_WARN_INFINITE_RECURSION = YES; 180 | CLANG_WARN_INT_CONVERSION = YES; 181 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 182 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 183 | CLANG_WARN_UNREACHABLE_CODE = YES; 184 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 185 | COPY_PHASE_STRIP = YES; 186 | ENABLE_NS_ASSERTIONS = NO; 187 | ENABLE_STRICT_OBJC_MSGSEND = YES; 188 | GCC_C_LANGUAGE_STANDARD = gnu99; 189 | GCC_NO_COMMON_BLOCKS = YES; 190 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 191 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 192 | GCC_WARN_UNDECLARED_SELECTOR = YES; 193 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 194 | GCC_WARN_UNUSED_FUNCTION = YES; 195 | GCC_WARN_UNUSED_VARIABLE = YES; 196 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 197 | MTL_ENABLE_DEBUG_INFO = NO; 198 | SDKROOT = iphoneos; 199 | VALIDATE_PRODUCT = YES; 200 | }; 201 | name = Release; 202 | }; 203 | 58B511F01A9E6C8500147676 /* Debug */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | HEADER_SEARCH_PATHS = ( 207 | "$(inherited)", 208 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 209 | "$(SRCROOT)/../../../React/**", 210 | "$(SRCROOT)/../../react-native/React/**", 211 | ); 212 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 213 | OTHER_LDFLAGS = "-ObjC"; 214 | PRODUCT_NAME = RNSecureStorage; 215 | SKIP_INSTALL = YES; 216 | }; 217 | name = Debug; 218 | }; 219 | 58B511F11A9E6C8500147676 /* Release */ = { 220 | isa = XCBuildConfiguration; 221 | buildSettings = { 222 | HEADER_SEARCH_PATHS = ( 223 | "$(inherited)", 224 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 225 | "$(SRCROOT)/../../../React/**", 226 | "$(SRCROOT)/../../react-native/React/**", 227 | ); 228 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 229 | OTHER_LDFLAGS = "-ObjC"; 230 | PRODUCT_NAME = RNSecureStorage; 231 | SKIP_INSTALL = YES; 232 | }; 233 | name = Release; 234 | }; 235 | /* End XCBuildConfiguration section */ 236 | 237 | /* Begin XCConfigurationList section */ 238 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNSecureStorage" */ = { 239 | isa = XCConfigurationList; 240 | buildConfigurations = ( 241 | 58B511ED1A9E6C8500147676 /* Debug */, 242 | 58B511EE1A9E6C8500147676 /* Release */, 243 | ); 244 | defaultConfigurationIsVisible = 0; 245 | defaultConfigurationName = Release; 246 | }; 247 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNSecureStorage" */ = { 248 | isa = XCConfigurationList; 249 | buildConfigurations = ( 250 | 58B511F01A9E6C8500147676 /* Debug */, 251 | 58B511F11A9E6C8500147676 /* Release */, 252 | ); 253 | defaultConfigurationIsVisible = 0; 254 | defaultConfigurationName = Release; 255 | }; 256 | /* End XCConfigurationList section */ 257 | }; 258 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 259 | } 260 | -------------------------------------------------------------------------------- /ios/RNSecureStorage.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | 3 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/RNSecureStorageHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class RNSecureStorageHelper { 4 | let appBundleName = Bundle.main.bundleIdentifier! 5 | enum KeychainError: Error { 6 | case keyRequired 7 | } 8 | /* 9 | Store an item in keychain. 10 | */ 11 | func createKeychainValue(key: String, value: String, accessible:String) -> Bool { 12 | let keyData = appBundleName+"."+key 13 | let tag = keyData.data(using: .utf8)! 14 | let valData = value.data(using: .utf8) 15 | let query:[String : Any] = [ 16 | kSecClass as String: kSecClassKey, 17 | kSecAttrApplicationTag as String : tag, 18 | kSecValueData as String : valData!, 19 | kSecAttrAccessible as String: self.accessibleValue(accessible: accessible) 20 | ] 21 | 22 | SecItemDelete(query as CFDictionary) 23 | 24 | let status = SecItemAdd(query as CFDictionary, nil) 25 | 26 | return status == errSecSuccess 27 | 28 | } 29 | 30 | /* 31 | Get value from keychain by key. 32 | */ 33 | func getKeychainValue(key: String) -> Data? { 34 | let keyData = appBundleName+"."+key 35 | let tag = keyData.data(using: .utf8)! 36 | 37 | let query: [String : Any] = [ 38 | kSecClass as String: kSecClassKey, 39 | kSecAttrApplicationTag as String: tag, 40 | kSecReturnData as String: kCFBooleanTrue!, 41 | ] 42 | var dataTypeRef: AnyObject? = nil 43 | 44 | let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) 45 | 46 | if status == noErr { 47 | return dataTypeRef as! Data? 48 | } else { 49 | return nil 50 | } 51 | 52 | } 53 | /* 54 | Check if key exist in keychain. 55 | */ 56 | func keychainValueExist(key: String) -> Bool { 57 | let keyData = appBundleName+"."+key 58 | let tag = keyData.data(using: .utf8)! 59 | 60 | let query: [String : Any] = [ 61 | kSecClass as String: kSecClassKey, 62 | kSecAttrApplicationTag as String: tag, 63 | kSecReturnData as String: kCFBooleanTrue!, 64 | ] 65 | 66 | let status = SecItemCopyMatching(query as CFDictionary, nil) 67 | 68 | return status == errSecSuccess 69 | 70 | } 71 | 72 | /* 73 | Get all stored keys from keychain. 74 | */ 75 | func getAllKeychainKeys() -> [String] { 76 | var keys = [String]() 77 | let query: [String: Any] = [ 78 | kSecClass as String : kSecClassKey, 79 | kSecReturnData as String : kCFBooleanTrue!, 80 | kSecReturnAttributes as String : kCFBooleanTrue!, 81 | kSecMatchLimit as String: kSecMatchLimitAll 82 | ] 83 | 84 | var result: AnyObject? = nil 85 | 86 | let lastResultCode = withUnsafeMutablePointer(to: &result) { 87 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) 88 | } 89 | 90 | if lastResultCode == noErr { 91 | let array = result as? Array> 92 | 93 | for item in array! { 94 | let key = item[kSecAttrApplicationTag as String] 95 | let replaceKey = String(data:key as! Data, encoding: .utf8)! 96 | let result = replaceKey.replacingOccurrences(of: appBundleName+".", with: "") 97 | keys.append(result) 98 | } 99 | } 100 | 101 | return keys 102 | 103 | } 104 | 105 | /* 106 | Set multiple values from keychain with keys 107 | */ 108 | func multiSetKeychainValues(keyValuePairs: [String: String], accessible: String) -> Bool { 109 | var settedPairs = 0 110 | for (key, value) in keyValuePairs { 111 | let val = self.createKeychainValue(key: key, value: value, accessible: accessible) 112 | if val { 113 | settedPairs += 1 114 | } 115 | } 116 | return settedPairs == keyValuePairs.count 117 | } 118 | 119 | 120 | /* 121 | Get multiple values from keychain with keys 122 | */ 123 | func multiGetKeychainValues(keys: [String]) throws -> [String: String?] { 124 | guard !keys.isEmpty else { 125 | throw RNSecureErrors.KEY_REQUIRED 126 | } 127 | 128 | var values = [String: String?]() 129 | for key in keys { 130 | if let val = self.getKeychainValue(key: key) { 131 | values[key] = String(data: val, encoding: .utf8) 132 | } else { 133 | values[key] = nil 134 | } 135 | } 136 | return values 137 | } 138 | 139 | 140 | /* 141 | Remove keychain item. 142 | */ 143 | func removeKeychainValue(key: String) -> Bool { 144 | let keyData = appBundleName+"."+key 145 | let tag = keyData.data(using: .utf8)! 146 | 147 | let query: [String : Any] = [ 148 | kSecClass as String: kSecClassKey, 149 | kSecAttrApplicationTag as String: tag, 150 | kSecReturnData as String: kCFBooleanTrue!, 151 | ] 152 | 153 | return SecItemDelete(query as CFDictionary) == errSecSuccess 154 | } 155 | 156 | /* 157 | Multi remove keychain item. 158 | */ 159 | func multiRemoveKeychainValue(keys: [String]) -> [String] { 160 | var unremovedKeys = [String]() 161 | for key in keys { 162 | let val = self.removeKeychainValue(key: key) 163 | if !val { 164 | unremovedKeys.append(key) 165 | } 166 | } 167 | return unremovedKeys 168 | } 169 | 170 | /* 171 | Clear all stored keys 172 | */ 173 | func clearAllStoredValues() -> [String] { 174 | var keys = [String]() 175 | var unremovedKeys = [String]() 176 | let query: [String: Any] = [ 177 | kSecClass as String : kSecClassKey, 178 | kSecReturnData as String : kCFBooleanTrue!, 179 | kSecReturnAttributes as String : kCFBooleanTrue!, 180 | kSecMatchLimit as String: kSecMatchLimitAll 181 | ] 182 | 183 | var result: AnyObject? = nil 184 | 185 | let lastResultCode = withUnsafeMutablePointer(to: &result) { 186 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) 187 | } 188 | 189 | if lastResultCode == noErr { 190 | let array = result as? Array> 191 | 192 | for item in array! { 193 | let key = item[kSecAttrApplicationTag as String] 194 | let replaceKey = String(data:key as! Data, encoding: .utf8)! 195 | let result = replaceKey.replacingOccurrences(of: appBundleName+".", with: "") 196 | keys.append(result) 197 | } 198 | } 199 | 200 | for key in keys { 201 | let status = self.removeKeychainValue(key: key) 202 | if !status { 203 | unremovedKeys.append(key) 204 | } 205 | } 206 | return unremovedKeys 207 | } 208 | 209 | func accessibleValue(accessible: String) -> CFString { 210 | 211 | let list = [ 212 | "AccessibleWhenUnlocked": kSecAttrAccessibleWhenUnlocked, 213 | "AccessibleAfterFirstUnlock": kSecAttrAccessibleAfterFirstUnlock, 214 | "AccessibleAlways": kSecAttrAccessibleAlways, 215 | "AccessibleWhenPasscodeSetThisDeviceOnly": kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, 216 | "AccessibleWhenUnlockedThisDeviceOnly": kSecAttrAccessibleWhenUnlockedThisDeviceOnly, 217 | "AccessibleAfterFirstUnlockThisDeviceOnly": kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, 218 | "AccessibleAlwaysThisDeviceOnly": kSecAttrAccessibleAlwaysThisDeviceOnly 219 | ] 220 | 221 | return list[accessible]! 222 | 223 | } 224 | 225 | 226 | } 227 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rn-secure-storage", 3 | "version": "3.0.1", 4 | "description": "Secure Storage for React Native (Android & iOS) - Keychain & Keystore", 5 | "main": "index.js", 6 | "types": "rn-secure-storage.d.ts", 7 | "keywords": [ 8 | "react-native", 9 | "ios", 10 | "android", 11 | "KeyChain service", 12 | "KeyStore service", 13 | "Device Security", 14 | "keystore", 15 | "keychain", 16 | "secure-preferences", 17 | "token", 18 | "react-token", 19 | "multiple values", 20 | "multiple keys" 21 | ], 22 | "author": "Talut TASGIRAN (https://talut.dev)", 23 | "license": "MIT", 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/talut/rn-secure-storage.git" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/talut/rn-secure-storage/issues" 30 | }, 31 | "homepage": "https://github.com/talut/rn-secure-storage#readme" 32 | } 33 | -------------------------------------------------------------------------------- /rn-secure-storage.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for rn-secure-storage 3.0.0 2 | // Project: https://github.com/akiver/rn-secure-storage 3 | // Definitions by: Talut TASGIRAN 4 | // TypeScript Version: 3.9.6 5 | 6 | declare module "rn-secure-storage" { 7 | export enum ACCESSIBLE { 8 | /** 9 | * The data in the keychain item cannot be accessed after a restart until the device 10 | * has been unlocked once by the user. 11 | */ 12 | AFTER_FIRST_UNLOCK = "AccessibleAfterFirstUnlock", 13 | /** 14 | * The data in the keychain item cannot be accessed after a restart until the device 15 | * has been unlocked once by the user. 16 | * Items with this attribute never migrate to a new device. 17 | */ 18 | AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY = "AccessibleAfterFirstUnlockThisDeviceOnly", 19 | /** 20 | * The data in the keychain item can always be accessed regardless of whether 21 | * the device is locked. 22 | */ 23 | ALWAYS = "AccessibleAlways", 24 | /** 25 | * The data in the keychain item can always be accessed regardless of whether the 26 | * device is locked. 27 | * Items with this attribute never migrate to a new device. 28 | */ 29 | ALWAYS_THIS_DEVICE_ONLY = "AccessibleAlwaysThisDeviceOnly", 30 | /** 31 | * The data in the keychain can only be accessed when the device is unlocked. 32 | * Only available if a passcode is set on the device. 33 | * Items with this attribute never migrate to a new device. 34 | */ 35 | WHEN_PASSCODE_SET_THIS_DEVICE_ONLY = "AccessibleWhenPasscodeSetThisDeviceOnly", 36 | /** 37 | * The data in the keychain item can be accessed only while the device is 38 | * unlocked by the user. 39 | * This is the default value. 40 | */ 41 | WHEN_UNLOCKED = "AccessibleWhenUnlocked", 42 | /** 43 | * The data in the keychain item can be accessed only while the device is 44 | * unlocked by the user. 45 | * Items with this attribute do not migrate to a new device. 46 | */ 47 | WHEN_UNLOCKED_THIS_DEVICE_ONLY = "AccessibleWhenUnlockedThisDeviceOnly", 48 | } 49 | 50 | 51 | type SetOptions = { 52 | /** 53 | * iOS ONLY! 54 | * This indicates when a keychain item is accessible, see possible values in RNSecureStorage.ACCESSIBLE. 55 | * Default: ACCESSIBLE.WHEN_UNLOCKED 56 | */ 57 | accessible?: ACCESSIBLE, 58 | } 59 | 60 | const RNSecureStorage: RNSecureStorageStatic; 61 | 62 | export interface RNSecureStorageStatic { 63 | /** 64 | * Set a value. 65 | */ 66 | setItem(key: string, value: string, options: SetOptions): Promise; 67 | 68 | /** 69 | * Get a value from secure storage. 70 | */ 71 | getItem(key: string): Promise; 72 | 73 | /** 74 | * Checks if a key has been set. 75 | */ 76 | exist(key: string): Promise; 77 | 78 | /** 79 | * Get all keys from secure storage. 80 | */ 81 | getAllKeys(): Promise; 82 | 83 | /** 84 | * Multiple key pair set for secure storage 85 | */ 86 | multiSet(pairs: { [key: string]: string }, options: SetOptions): Promise; 87 | 88 | /** 89 | * Get multiple values from secure storage. 90 | */ 91 | multiGet(keys: string[]): Promise<{[key: string]: string} | null>; 92 | 93 | /** 94 | * Remove a value from secure storage. 95 | */ 96 | removeItem(key: string): Promise; 97 | 98 | /** 99 | * Remove values from secure storage (On error will return unremoved keys) 100 | */ 101 | multiRemove(keys: string[]): Promise; 102 | 103 | /** 104 | * Removes whole RNSecureStorage data (On error will return unremoved keys) 105 | */ 106 | clear(): Promise; 107 | } 108 | 109 | export default RNSecureStorage; 110 | } 111 | -------------------------------------------------------------------------------- /sample/.bundle/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= 65 | env_var_version || cli_arg_version || 66 | lockfile_version 67 | end 68 | 69 | def bundler_requirement 70 | return "#{Gem::Requirement.default}.a" unless bundler_version 71 | 72 | bundler_gem_version = Gem::Version.new(bundler_version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /sample/.bundle/bin/fuzzy_match: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'fuzzy_match' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("fuzzy_match", "fuzzy_match") 30 | -------------------------------------------------------------------------------- /sample/.bundle/bin/httpclient: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'httpclient' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("httpclient", "httpclient") 30 | -------------------------------------------------------------------------------- /sample/.bundle/bin/pod: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'pod' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("cocoapods", "pod") 30 | -------------------------------------------------------------------------------- /sample/.bundle/bin/sandbox-pod: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sandbox-pod' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("cocoapods", "sandbox-pod") 30 | -------------------------------------------------------------------------------- /sample/.bundle/bin/xcodeproj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'xcodeproj' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("xcodeproj", "xcodeproj") 30 | -------------------------------------------------------------------------------- /sample/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /sample/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["@react-native-community", "prettier"], 4 | parser: "@typescript-eslint/parser", 5 | plugins: ["@typescript-eslint"], 6 | }; 7 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | ios/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # fastlane 44 | # 45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 46 | # screenshots whenever they are needed. 47 | # For more information about the recommended setup visit: 48 | # https://docs.fastlane.tools/best-practices/source-control/ 49 | 50 | **/fastlane/report.xml 51 | **/fastlane/Preview.html 52 | **/fastlane/screenshots 53 | **/fastlane/test_output 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Ruby / CocoaPods 59 | /ios/Pods/ 60 | /vendor/bundle/ 61 | 62 | # Temporary files created by Metro to check the health of the file watcher 63 | .metro-health-check* 64 | 65 | # testing 66 | /coverage 67 | -------------------------------------------------------------------------------- /sample/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | jsxBracketSameLine: true, 4 | singleQuote: false, 5 | doubleQuote: true, 6 | trailingComma: "all", 7 | arrowParens: "avoid", 8 | printWidth: 140, 9 | parser: "typescript", 10 | useTabs: true, 11 | }; 12 | -------------------------------------------------------------------------------- /sample/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /sample/App.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | import React from "react"; 9 | import { Animated, Button, SafeAreaView, StyleSheet, Text, View } from "react-native"; 10 | import RNSecureStorage, { ACCESSIBLE } from "rn-secure-storage"; 11 | import ScrollView = Animated.ScrollView; 12 | 13 | 14 | const options = { 15 | accessible: ACCESSIBLE.WHEN_UNLOCKED 16 | }; 17 | 18 | function App(): React.JSX.Element { 19 | 20 | 21 | const [message, setMessage] = React.useState(null); 22 | const [error, setError] = React.useState(null); 23 | const setItem = () => { 24 | RNSecureStorage.setItem("idToken", "sdoi34y5o34webfld,v sv", { 25 | accessible: ACCESSIBLE.WHEN_UNLOCKED 26 | }).then(res => { 27 | setMessage(res); 28 | setError(null); 29 | }).catch(err => { 30 | setError(err); 31 | setMessage(null); 32 | } 33 | ); 34 | }; 35 | 36 | const getItem = () => { 37 | RNSecureStorage.getItem("idToken").then(res => { 38 | setMessage(res); 39 | setError(null); 40 | }).catch(err => { 41 | setError(err); 42 | setMessage(null); 43 | } 44 | ); 45 | }; 46 | 47 | const removeItem = () => { 48 | RNSecureStorage.removeItem("idToken").then(res => { 49 | setMessage(res); 50 | setError(null); 51 | }).catch(err => { 52 | setError(err); 53 | setMessage(null); 54 | } 55 | ); 56 | }; 57 | 58 | const getAllKeys = () => { 59 | RNSecureStorage.getAllKeys().then(res => { 60 | if (res) setMessage(res.join(", ")); 61 | setError(null); 62 | }).catch(err => { 63 | setError(err); 64 | setMessage(null); 65 | } 66 | ); 67 | }; 68 | 69 | const exist = () => { 70 | RNSecureStorage.exist("idToken").then(res => { 71 | setMessage(res ? "Exists" : "Not exists"); 72 | setError(null); 73 | }).catch(err => { 74 | setError(err); 75 | setMessage(null); 76 | } 77 | ); 78 | }; 79 | 80 | const clear = () => { 81 | RNSecureStorage.clear().then(res => { 82 | setMessage(res); 83 | setError(null); 84 | }).catch(err => { 85 | setError(err); 86 | setMessage(null); 87 | } 88 | ); 89 | }; 90 | 91 | 92 | const multiSet = () => { 93 | const items = { 94 | "multikey1": "multikey1 value", 95 | "multikey2": "multiksdfklhds,v xo4yrotrhukjsbngey2 value" 96 | }; 97 | 98 | 99 | RNSecureStorage.multiSet(items, { 100 | accessible: ACCESSIBLE.WHEN_UNLOCKED 101 | }).then(res => { 102 | if (res) setMessage(JSON.stringify(res)); 103 | setError(null); 104 | }).catch(err => { 105 | setError(err); 106 | setMessage(null); 107 | }); 108 | }; 109 | 110 | const multiGet = () => { 111 | RNSecureStorage.multiGet(["multikey1", "multikey2", "multikey3"]).then(res => { 112 | if (res) setMessage(JSON.stringify(res)); 113 | setError(null); 114 | }).catch(err => { 115 | setError(err); 116 | setMessage(null); 117 | } 118 | ); 119 | }; 120 | 121 | const multiRemove = () => { 122 | RNSecureStorage.multiRemove(["multikey1", "multikey3"]).then(res => { 123 | console.log(res); 124 | setMessage(res); 125 | setError(null); 126 | } 127 | ).catch(err => { 128 | console.log("err", err); 129 | setError(err); 130 | setMessage(null); 131 | }); 132 | }; 133 | 134 | 135 | return ( 136 | 137 | 138 | 139 | 140 |