UNPKG

rn-secure-keystore

Version:

A comprehensive, cross-platform React Native wrapper for secure key-value storage using native security features of Android and iOS. It supports **biometric authentication**, **hardware-backed encryption**, and deep platform integrations such as **Android

672 lines (482 loc) 19.7 kB
# rn-secure-keystore A comprehensive, cross-platform React Native wrapper for secure key-value storage using native security features of Android and iOS. It supports **biometric authentication**, **hardware-backed encryption**, and deep platform integrations such as **Android StrongBox**, **EncryptedSharedPreferences**, and iOS Secure Enclave via the Keychain. This library enables storing data securely with biometric protection, ensuring that sensitive information can only be accessed after successful biometric verification (e.g., fingerprint or face recognition). This adds an additional layer of security by requiring user authentication to retrieve or modify protected data, making it ideal for handling highly sensitive data. ## Features - **Hardware-backed security** – Utilizes device secure elements when available - **Biometric authentication** – Store and retrieve data with biometric protection - **Cross-platform** – Works on both iOS and Android with platform-specific optimizations - **StrongBox support** _(Android only)_ – Enhanced protection using dedicated security chips - **iOS Keychain integration** – Access to iOS Keychain access controls - **Secure storage** – Store sensitive data like tokens, passwords, and user credentials - **Error handling** – Comprehensive error codes and recovery mechanisms - **Security introspection** – Check security levels and platform capabilities - **Fallback mechanisms** – Graceful degradation when advanced features aren't available ## 📦 Installation ```bash npm install rn-secure-keystore ``` ### iOS Setup ```bash cd ios && pod install ``` Add the following to your `Info.plist`: ```xml <key>NSFaceIDUsageDescription</key> <string>Use Face ID to authenticate and access secure data</string> ``` ### Android Setup Ensure your `android/app/build.gradle` has minimum SDK version 23: ```gradle android { compileSdkVersion 34 defaultConfig { minSdkVersion 23 // ... } } ``` Add biometric permissions to `android/app/src/main/AndroidManifest.xml`: ```xml <uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_FINGERPRINT" /> ``` > **Note**: This library requires native modules and will not work with **Expo Go**. ## Platform-Level Security Features ### iOS – Secure Enclave & Keychain On iOS, all data is securely stored using the native **Keychain**. For devices with **Secure Enclave** (iPhone 5s and later), encryption is **hardware-backed**, meaning your secrets are tied to the physical chip and are secure. **Key benefits:** - Secure Enclave for hardware-backed storage - Support for **access groups** and **keychain sharing** between apps - Advanced access control options - Hardware backing when available ### Android – EncryptedSharedPreferences & StrongBox #### EncryptedSharedPreferences This library uses Android Jetpack's `EncryptedSharedPreferences` with fallback to regular SharedPreferences, which combines: - A **master key** stored in Android's **Keystore** - AES encryption for your key-value pairs - Automatic key generation and rotation - Graceful fallback for compatibility #### StrongBox On supported devices (API 28+), you can optionally store encryption keys in **StrongBox**, a **hardware security module (HSM)** separate from the main CPU. This provides maximum protection, especially against physical tampering. - Resistant to rooting and debugging - Keys stored in StrongBox are **non-exportable** - Enforced user authentication (e.g., biometrics) via hardware-backed checks - **Fallback to TEE (Trusted Execution Environment)** if StrongBox is unavailable > StrongBox is only available on select newer Android devices with a dedicated secure element (Pixel 3+, Samsung S9+, etc.). ## 🔐 Security Levels ### Android Security Tiers | Level | Description | Use Case | | ----------- | -------------------------------------------------------- | -------------------- | | `strongbox` | Stored in StrongBox HSM; maximum protection if available | Ultra-sensitive data | | `hardware` | Backed by Trusted Execution Environment (TEE) | Sensitive data | | `software` | Software-encrypted; fallback for older devices | Basic protection | | `auto` | Automatically chooses the strongest supported level | Recommended default | ### iOS Security - iOS Keychain automatically uses **hardware-backed encryption** when the device supports **Secure Enclave** - Hardware backing is **transparent and automatic** on supported iOS devices ## Quick Start ### Basic Usage ```typescript import SecureStorage from 'rn-secure-keystore'; // Store a value await SecureStorage.setItem('userToken', 'abc123'); // Retrieve a value const token = await SecureStorage.getItem('userToken'); // Check if a key exists const exists = await SecureStorage.hasItem('userToken'); // Remove a value await SecureStorage.removeItem('userToken'); // Get all keys const keys = await SecureStorage.getAllKeys(); // Clear all data await SecureStorage.clear(); ``` ### Biometric Authentication ```typescript // Store with biometric protection await SecureStorage.setItem('sensitiveData', 'secret', { withBiometric: true, authenticatePrompt: 'Authenticate to store sensitive data', authenticatePromptSubtitle: 'Use your fingerprint or face to secure this data', }); // Retrieve with biometric authentication const data = await SecureStorage.getItem('sensitiveData', { authenticatePrompt: 'Authenticate to access sensitive data', authenticatePromptSubtitle: 'Use your biometric to continue', }); ``` ## Platform-Specific Features ### Android – Advanced Security Options ```typescript // Check device capabilities const capabilities = await SecureStorage.getPlatformCapabilities(); console.log('StrongBox available:', capabilities.hasStrongBox); // Store with specific security level await SecureStorage.setItem('data', 'value', { securityLevel: 'strongbox', // 'strongbox' | 'hardware' | 'software' | 'auto' allowFallback: true, // Allow fallback if StrongBox not available }); // Use convenience method for StrongBox await SecureStorage.setStrongBoxItem('ultraSecure', 'data', true); // Get recommended security level const recommendedLevel = await SecureStorage.getRecommendedSecurityLevel(); ``` ### iOS – Keychain Access Control ```typescript import SecureStorage, { ACCESS_CONTROL } from 'rn-secure-keystore'; // Store with specific access control (no biometric required for basic storage) await SecureStorage.setItem('basicData', 'value'); // Store with biometric requirement await SecureStorage.setItem('biometricData', 'secret', { accessControl: ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE, authenticatePrompt: 'Authenticate to store data', }); // Use convenience method await SecureStorage.setKeychainItem( 'keychainData', 'secret', ACCESS_CONTROL.BIOMETRY_CURRENT_SET, 'com.yourapp.shared' // access group (optional) ); ``` ## Security Introspection ```typescript // Check individual key security const isHardwareBacked = await SecureStorage.isKeyHardwareBacked('myKey'); const securityLevel = await SecureStorage.getKeySecurityLevel('myKey'); // Get security status for all keys const securityStatus = await SecureStorage.getSecurityStatus(); console.log('All keys security:', securityStatus); // Check if device meets requirements const requirements = { requireBiometric: true, requireHardwareBacking: true, requireStrongBox: false, }; const { meets, missing } = await SecureStorage.meetsSecurityRequirements(requirements); ``` ## Complete API Reference ### Core Storage Methods #### `setItem(key: string, value: string, options?: StorageOptions): Promise<boolean>` Stores a key-value pair securely with optional biometric protection and security level specification. **Parameters:** - `key`: Unique identifier for the stored value - `value`: Data to store securely - `options`: Optional configuration object **Example:** ```typescript await SecureStorage.setItem('apiKey', 'sk-123456', { withBiometric: true, securityLevel: 'hardware', // Android only accessControl: ACCESS_CONTROL.BIOMETRY_ANY, // iOS only }); ``` #### `getItem(key: string, options?: GetItemOptions): Promise<string | null>` Retrieves a stored value, prompting for authentication if required. #### `removeItem(key: string): Promise<boolean>` Removes a stored key-value pair from secure storage. #### `hasItem(key: string): Promise<boolean>` Checks if a key exists in storage without retrieving the value. #### `getAllKeys(): Promise<string[]>` Returns an array of all stored keys. #### `clear(): Promise<boolean>` Clears all secure storage and removes all encryption keys. ### Security & Capability Methods #### `isBiometricAvailable(): Promise<boolean>` Checks if biometric authentication is available and enrolled on the device. #### `isHardwareBackedAvailable(): Promise<boolean>` Checks if hardware-backed keystore is available on the current device. #### `isStrongBoxAvailable(): Promise<boolean>` Checks if StrongBox hardware security module is available (Android only). #### `getHardwareSecurityInfo(): Promise<HardwareSecurityInfo>` Returns comprehensive information about device security capabilities. ```typescript const info = await SecureStorage.getHardwareSecurityInfo(); // { // isHardwareBackedAvailable: true, // isStrongBoxAvailable: true, // recommendedSecurityLevel: 'strongbox' // } ``` #### `getPlatformCapabilities(): Promise<PlatformCapabilities>` Returns detailed platform-specific feature availability. #### `isKeyHardwareBacked(key: string): Promise<boolean>` Checks if a specific stored key uses hardware-backed security. #### `getKeySecurityLevel(key: string): Promise<string>` Returns the security level of a specific key ('strongbox', 'hardware', 'software', 'unknown'). #### `getRecommendedSecurityLevel(): Promise<'strongbox' | 'hardware' | 'software'>` Returns the recommended security level for the current device. #### `getSecurityStatus(): Promise<SecurityStatus>` Returns security information for all stored keys. #### `meetsSecurityRequirements(requirements): Promise<{meets: boolean, missing: string[]}>` Checks if the current device meets specified security requirements. ### Platform-Specific Methods #### Android Only ##### `setStrongBoxItem(key: string, value: string, allowFallback?: boolean): Promise<boolean>` Convenience method to store data with StrongBox security. #### iOS Only ##### `setKeychainItem(key: string, value: string, accessControl?: string, accessGroup?: string): Promise<boolean>` Convenience method for iOS Keychain with custom access control. ### Utility Methods #### `migrateToSecureStorage(key: string, plainValue: string, options?: StorageOptions): Promise<boolean>` Migrates plain text data to secure storage. #### `isSecurityLevelAvailable(level: 'strongbox' | 'hardware'): Promise<boolean>` Checks if a specific security level is available on the current device. ## Type Definitions ### StorageOptions ```typescript interface StorageOptions { withBiometric?: boolean; // Android-specific options requireStrongBox?: boolean; requireHardware?: boolean; securityLevel?: 'auto' | 'strongbox' | 'hardware' | 'software'; allowFallback?: boolean; // iOS-specific options accessGroup?: string | null; accessControl?: string | null; // Authentication prompts authenticatePrompt?: string; authenticatePromptSubtitle?: string; } ``` ### GetItemOptions ```typescript interface GetItemOptions { // iOS-specific accessGroup?: string | null; kLocalizedFallbackTitle?: string; // Android-specific showModal?: boolean; // Common authenticatePrompt?: string; authenticatePromptSubtitle?: string; } ``` ### HardwareSecurityInfo ```typescript interface HardwareSecurityInfo { isHardwareBackedAvailable: boolean; isStrongBoxAvailable: boolean; recommendedSecurityLevel: 'strongbox' | 'hardware' | 'software'; } ``` ### PlatformCapabilities ```typescript interface PlatformCapabilities { platform: string; hasStrongBox: boolean; hasHardwareBackedKeystore: boolean; hasBiometrics: boolean; hasKeychainAccessControl: boolean; } ``` ## Constants ### iOS Access Control ```typescript import { ACCESS_CONTROL } from 'rn-secure-keystore'; const ACCESS_CONTROL = { BIOMETRY_ANY: 'kSecAccessControlBiometryAny', BIOMETRY_CURRENT_SET: 'kSecAccessControlBiometryCurrentSet', DEVICE_PASSCODE: 'kSecAccessControlDevicePasscode', APPLICATION_PASSWORD: 'kSecAccessControlApplicationPassword', BIOMETRY_ANY_OR_DEVICE_PASSCODE: 'kSecAccessControlBiometryAnyOrDevicePasscode', }; ``` ### Error Codes ```typescript import { ERROR_CODES } from 'rn-secure-keystore'; // Authentication errors ERROR_CODES.AUTHENTICATION_CANCELLED; ERROR_CODES.AUTHENTICATION_FAILED; ERROR_CODES.BIOMETRIC_NOT_AVAILABLE; // Platform errors ERROR_CODES.PLATFORM_NOT_SUPPORTED; ERROR_CODES.STRONGBOX_NOT_AVAILABLE; // Storage errors ERROR_CODES.STORAGE_ERROR; ERROR_CODES.RETRIEVAL_ERROR; // ... and more ``` ## Error Handling The library provides comprehensive error handling with detailed error codes: ```typescript import { SecureStorageError, ERROR_CODES } from 'rn-secure-keystore'; try { await SecureStorage.getItem('protectedKey'); } catch (error) { if (error instanceof SecureStorageError) { switch (error.code) { case ERROR_CODES.AUTHENTICATION_CANCELLED: console.log('User cancelled authentication'); break; case ERROR_CODES.BIOMETRIC_NOT_AVAILABLE: console.log('Biometric authentication not available'); break; case ERROR_CODES.STRONGBOX_NOT_AVAILABLE: console.log('StrongBox not supported on this device'); break; default: console.error('Storage error:', error.message); } } } ``` ## Best Practices ### 1. Check Capabilities First ```typescript const capabilities = await SecureStorage.getPlatformCapabilities(); if (capabilities.hasStrongBox) { // Use StrongBox for maximum security await SecureStorage.setStrongBoxItem('ultraSecret', 'value'); } else if (capabilities.hasHardwareBackedKeystore) { // Fallback to hardware backing await SecureStorage.setItem('secret', 'value', { securityLevel: 'hardware' }); } ``` ### 2. Handle Biometric Availability ```typescript const biometricAvailable = await SecureStorage.isBiometricAvailable(); if (biometricAvailable) { await SecureStorage.setItem('biometricData', 'secret', { withBiometric: true, }); } else { // Fallback to device passcode or other authentication await SecureStorage.setItem('data', 'secret'); } ``` ### 3. Use Appropriate Security Levels ```typescript // For highly sensitive data (credit cards, private keys) await SecureStorage.setItem('creditCard', cardNumber, { securityLevel: 'strongbox', withBiometric: true, allowFallback: true, }); // For moderately sensitive data (API tokens) await SecureStorage.setItem('apiToken', token, { securityLevel: 'hardware', allowFallback: true, }); // For less sensitive data (user preferences) await SecureStorage.setItem('userPrefs', preferences); ``` ### 4. Implement Proper Error Handling ```typescript try { const secret = await SecureStorage.getItem('protectedData', { authenticatePrompt: 'Access your secure data', }); } catch (error) { if (error instanceof SecureStorageError) { switch (error.code) { case ERROR_CODES.AUTHENTICATION_CANCELLED: // Don't show error, user intentionally cancelled break; case ERROR_CODES.AUTHENTICATION_FAILED: showRetryDialog(); break; default: showGenericErrorDialog(error.message); } } } ``` ### 5. Audit Security Status ```typescript const securityStatus = await SecureStorage.getSecurityStatus(); for (const [key, info] of Object.entries(securityStatus)) { console.log( `${key}: Hardware=${info.isHardwareBacked}, Level=${info.securityLevel}` ); } ``` ## Platform Differences ### Storage Options | Feature | iOS | Android | Notes | | ----------------- | ------------------------ | ------------------------ | ------------------------------------------------ | | Basic Storage | Hardware-backed keychain | Default Android Keystore | iOS automatically hardware-backed when available | | Hardware Storage | Same as basic | Explicit TEE backing | iOS: automatic, Android: explicit | | StrongBox | Not available | Dedicated security chip | Android-only feature | | Biometric Storage | Face ID/Touch ID | Fingerprint | Platform-native biometric APIs | ### Key Differences **iOS:** - Basic storage = Hardware storage (automatically hardware-backed) - Access control determines authentication requirements - All keychain items are secure when Secure Enclave is available **Android:** - Hardware ≠ StrongBox (explicit security levels) - Manual security level control - StrongBox provides highest security when available ## Troubleshooting ### Common Issues #### "The package doesn't seem to be linked" - Run `npx pod-install` (iOS) - Rebuild your app after installation - Avoid Expo Go (use development build) #### Multiple biometric prompts - Use single `setItem()` call with `withBiometric: true` - Don't call `getItem()` immediately after `setItem()` for biometric-protected data #### "bad base-64" error (Android) - Corrupted EncryptedSharedPreferences data - Use `clear()` method to clean up corrupted storage - Library automatically handles fallback to regular SharedPreferences #### StrongBox not available - StrongBox requires newer Android devices (API 28+) - Use `allowFallback: true` for broader compatibility - Check `isStrongBoxAvailable()` before using #### Authentication errors - Ensure biometrics are enrolled on device - Use clear `authenticatePrompt` messages - Handle user cancellations gracefully ### Debug Information Enable logging to see detailed storage operations: ```typescript // Check device capabilities const info = await SecureStorage.getHardwareSecurityInfo(); console.log('Security capabilities:', info); // Check individual key security const keyInfo = await SecureStorage.isKeyHardwareBacked('myKey'); console.log('Key hardware-backed:', keyInfo); ``` ## Requirements - **React Native**: 0.60+ - **iOS**: 11.0+ - **Android**: API level 23+ (Android 6.0+) ## Advanced Features ### Security Requirements Validation ```typescript const requirements = { requireBiometric: true, requireHardwareBacking: true, requireStrongBox: false, }; const { meets, missing } = await SecureStorage.meetsSecurityRequirements(requirements); if (!meets) { console.log('Missing security features:', missing); } ``` ### Migration from Plain Storage ```typescript // Migrate existing plain text data to secure storage await SecureStorage.migrateToSecureStorage('oldKey', plainTextValue, { securityLevel: 'hardware', withBiometric: true, }); ``` ## 📄 License MIT ## Contributing Contributions are welcome! Please open issues and pull requests with clear descriptions. ## Support For issues and questions: - GitHub Issues: Report bugs and feature requests - Documentation: Check this README for comprehensive API documentation - Examples: See the `/example` folder for complete usage examples --- **Made with ❤️ for React Native developers who care about security.**