rspl_secure_vault 0.0.3
rspl_secure_vault: ^0.0.3 copied to clipboard
Secure Flutter plugin for storing sensitive data with hardware-backed encryption (iOS Secure Enclave, Android Keystore).
Secure Vault #
This is a secure-by-default Flutter plugin for storing sensitive data. It provides automatic encryption using envelope encryption with platform-specific hardware-backed key management (iOS Secure Enclave/Keychain and Android Keystore).
β οΈ Security Notice: While this package implements industry-standard encryption (AES-256-GCM), always conduct independent security audits for production applications handling sensitive data. See Important Notes for Production Use for details.
Quick Start #
import 'package:rspl_secure_vault/rspl_secure_vault.dart';
final vault = RsplSecureVault();
await vault.initialize(bundleId: 'com.example.app');
// Store and retrieve - that's it!
await vault.store('token', 'secret-value');
final value = await vault.retrieve('token'); // Returns: 'secret-value'
Table of Contents #
- Features
- Platform Support
- Android API Support
- Requirements
- Permissions Required
- Getting Started
- Usage
- API Reference
- Error Handling
- Limitations
- Architecture
- Security Audit
- Security Details
- Design Philosophy
- Security Model & Limitations
- Best Practices
- Troubleshooting
- Important Notes for Production Use
- Compliance & Standards
- FAQ
- Folder Structure
- Example
- Acknowledgments
- Contributing
- User Privacy Notes
- Author, Maintainers & Acknowledgements
- License
Features #
- π Secure by Default: All data is automatically encrypted before storage
- π‘οΈ Hardware-Backed Security:
- iOS: Secure Enclave + Keychain
- Android: Android Keystore with StrongBox support (when available)
- π― Simple API: Just
store,retrieve,remove, andclear - π Envelope Encryption: AES-256-GCM with unique keys per operation
- π Secure Key Exchange: ECDH (P-256/secp256r1) for key agreement
- β Tamper Detection: GCM authentication tags prevent data modification
- π± Cross-Platform: Unified API for iOS and Android
- π« Hard to Misuse: No configuration options that could weaken security
- π Backup/Restore Safe: Automatic detection and handling of orphaned data
Platform Support #
| Platform | Minimum Version | Notes |
|---|---|---|
| Android | API 24 (Android 7.0+) | Full hardware-backed security |
| iOS | iOS 13.0+ | Secure Enclave on supported devices |
Android API Support #
| API Level | Android Version | ECDH Implementation | Notes |
|---|---|---|---|
| 31+ | Android 12+ | Native Keystore ECDH | Best performance, direct hardware support |
| 24-30 | Android 7-11 | AES-wrapped EC key | Hybrid approach, same security level |
Note: On API 24-30, the package uses an AES master key in Keystore to protect the EC key, since
PURPOSE_AGREE_KEYwas only added in API 31. Security is equivalent; only the internal implementation differs.
Requirements #
- Dart: >=3.0.0 <4.0.0
- Flutter: Flutter 3.3.0+ based on Dart 3.0.0
- iOS: >=13.0
- Android: API 24+ (Android 7.0+)
Permissions Required #
- Android: No explicit permissions required (Keystore access is automatic)
- iOS: No explicit permissions required (Keychain/Secure Enclave access is automatic)
Getting Started #
1) Install #
Add the dependency to your pubspec.yaml:
dependencies:
rspl_secure_vault: ^0.0.3
Then run:
flutter pub get
2) Import #
import 'package:rspl_secure_vault/rspl_secure_vault.dart';
3) Initialize (Recommended: in main()) #
import 'package:flutter/material.dart';
import 'package:rspl_secure_vault/rspl_secure_vault.dart';
late final RsplSecureVault vault;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
vault = RsplSecureVault();
await vault.initialize(bundleId: 'com.example.myapp');
runApp(const MyApp());
}
Best Practice: Initialize once in
main()beforerunApp(). This ensures the vault is ready before any widget needs it and catches initialization errors early.
Bundle ID Clarification #
Bundle ID Usage: Use your app's actual bundle identifier (iOS) or application ID (Android).
// Use your actual app identifier
await vault.initialize(bundleId: 'com.yourcompany.yourapp');
β οΈ Important:
- The same bundle ID must be used consistently across app launches
- Changing the bundle ID will make existing data unreadable (keys are tied to the identifier)
- Different bundle IDs create completely separate key stores (useful for multi-app scenarios)
Usage #
Basic Usage #
import 'package:rspl_secure_vault/rspl_secure_vault.dart';
// Create and initialize the vault (do this once at app startup)
final vault = RsplSecureVault();
await vault.initialize(bundleId: 'com.example.myapp');
// Store sensitive data (automatically encrypted)
await vault.store('api_token', 'secret-token-value');
await vault.store('refresh_token', 'refresh-token-value');
// Retrieve data (automatically decrypted)
final token = await vault.retrieve('api_token');
print('Token: $token');
// Check if a key exists
if (await vault.containsKey('api_token')) {
print('Token exists!');
}
// Remove specific data
await vault.remove('api_token');
// Clear all stored data
await vault.clear();
Common Use Cases #
Storing Authentication Tokens
class AuthService {
final _vault = RsplSecureVault();
Future<void> saveTokens({
required String accessToken,
required String refreshToken,
}) async {
await _vault.store('access_token', accessToken);
await _vault.store('refresh_token', refreshToken);
}
Future<String?> getAccessToken() => _vault.retrieve('access_token');
Future<String?> getRefreshToken() => _vault.retrieve('refresh_token');
Future<void> clearTokens() async {
await _vault.remove('access_token');
await _vault.remove('refresh_token');
}
}
Storing API Keys
class ApiKeyManager {
final _vault = RsplSecureVault();
Future<void> saveApiKey(String serviceName, String apiKey) async {
await _vault.store('api_key_$serviceName', apiKey);
}
Future<String?> getApiKey(String serviceName) async {
return await _vault.retrieve('api_key_$serviceName');
}
}
Complete Example with Error Handling #
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rspl_secure_vault/rspl_secure_vault.dart';
class SecureStorageService {
final _vault = RsplSecureVault();
bool _isInitialized = false;
Future<void> initialize() async {
if (_isInitialized) return;
try {
await _vault.initialize(bundleId: 'com.example.myapp');
_isInitialized = true;
} on PlatformException catch (e) {
debugPrint('Failed to initialize vault: ${e.message}');
rethrow;
}
}
Future<void> saveToken(String token) async {
try {
await _vault.store('auth_token', token);
} on PlatformException catch (e) {
debugPrint('Failed to save token: ${e.message}');
rethrow;
}
}
Future<String?> getToken() async {
try {
return await _vault.retrieve('auth_token');
} on PlatformException catch (e) {
debugPrint('Failed to retrieve token: ${e.message}');
return null;
}
}
Future<void> clearToken() async {
try {
await _vault.remove('auth_token');
} on PlatformException catch (e) {
debugPrint('Failed to clear token: ${e.message}');
}
}
Future<void> logout() async {
try {
await _vault.clear();
} on PlatformException catch (e) {
debugPrint('Failed to clear vault: ${e.message}');
}
}
}
API Reference #
RsplSecureVault #
The main class for secure storage operations. Uses a singleton pattern.
Constructor
final vault = RsplSecureVault();
Properties
| Property | Type | Description |
|---|---|---|
isInitialized |
bool |
Whether the vault has been initialized |
Methods
| Method | Return Type | Description |
|---|---|---|
initialize({required String bundleId, bool clearOnKeyMismatch = true}) |
Future<void> |
Initializes the vault with the app's bundle ID |
store(String key, String value) |
Future<void> |
Stores a value securely (encrypted) |
retrieve(String key) |
Future<String?> |
Retrieves and decrypts a value |
remove(String key) |
Future<void> |
Removes a specific key-value pair |
clear() |
Future<void> |
Removes all stored values |
containsKey(String key) |
Future<bool> |
Checks if a key exists |
Error Handling #
All methods throw PlatformException on errors:
try {
await vault.store('key', 'value');
} on PlatformException catch (e) {
switch (e.code) {
case 'UNINITIALIZED':
print('Vault not initialized. Call initialize() first.');
break;
case 'INVALID_KEY':
print('Key cannot be empty.');
break;
case 'INVALID_VALUE':
print('Value cannot be empty.');
break;
case 'ENCRYPTION_FAILED':
print('Encryption operation failed. Check device security settings.');
break;
case 'DECRYPTION_FAILED':
print('Decryption failed. Data may be corrupted or keys changed.');
break;
default:
print('Operation failed: ${e.message}');
}
}
Error Codes #
| Code | Description | Common Cause |
|---|---|---|
UNINITIALIZED |
Vault not initialized | Forgot to call initialize() |
INVALID_KEY |
Key is empty | Passed empty string as key |
INVALID_VALUE |
Value is empty | Passed empty string as value |
ENCRYPTION_FAILED |
Encryption operation failed | Device security issue or key generation failure |
DECRYPTION_FAILED |
Decryption operation failed | Data corruption or key mismatch (backup/restore) |
channel-error |
Platform channel error | Native code exception |
Limitations #
Data Size Recommendations #
| Use Case | Recommended | Notes |
|---|---|---|
| Auth tokens | β Ideal | Typically < 2KB |
| API keys | β Ideal | Typically < 1KB |
| Small JSON configs | β Good | < 10KB |
| Large data (> 100KB) | β οΈ Not recommended | Use file encryption instead |
| Binary data / Files | β Not supported | Strings only |
Concurrency #
- Thread-safe: Multiple simultaneous operations are supported
- Initialization: Race-condition protected (concurrent
initialize()calls are safe) - Atomic operations: Each store/retrieve is atomic
Storage Limits #
- Keys: Any non-empty string (recommended: < 256 characters)
- Values: Any non-empty string (recommended: < 1MB for performance)
- Total entries: Limited by device storage (FlutterSecureStorage backend)
Architecture #
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Flutter App β
β β β
β RsplSecureVault β
β (Simple API) β
β store / retrieve / remove / clear β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Internal Implementation β
β βββββββββββββββββββββββ βββββββββββββββββββββββββββ β
β β Encryption Layer β β FlutterSecureStorage β β
β β (Envelope Crypto) β β (Persistent Storage) β β
β βββββββββββββββββββββββ βββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β Platform Channel
βββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββ
β Native Implementation β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ€
β iOS β Android β
β ββββββββββββββββββ β ββββββββββββββββββββββββββββββ β
β β Secure Enclave β β β Android Keystore β β
β β + Keychain β β β (StrongBox if available) β β
β ββββββββββββββββββ β ββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββ
Envelope Encryption Flow #
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ENCRYPTION β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 1. Generate random DEK (Data Encryption Key) β
β β β
β 2. Encrypt plaintext with DEK using AES-256-GCM β
β β β
β 3. Derive KEK via ECDH + HKDF β
β (Ephemeral key + Device master key) β
β β β
β 4. Wrap DEK with KEK using AES-256-GCM β
β β β
β 5. Store envelope: {wrappedDEK, ciphertext, nonces...} β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Security Audit #
This package has undergone an internal security code review covering:
- β API design and misuse prevention
- β Key lifecycle management
- β Encryption implementation (AES-256-GCM)
- β Nonce/IV handling
- β Envelope encryption architecture
- β Platform security (Secure Enclave / Android Keystore)
- β Error handling and logging practices
- β Backup/restore orphaned data handling
Audit Result: 62/62 checks passed
π View Full Security Audit Report
β οΈ Note: This is an internal code review, not a formal third-party penetration test. Users requiring certified audits should engage qualified security firms.
Security Details #
Envelope Encryption Flow #
- Key Generation: Random 256-bit Data Encryption Key (DEK) generated per operation
- Data Encryption: Plain text encrypted with AES-256-GCM using DEK
- Key Wrapping: DEK wrapped using Key Encryption Key (KEK) derived via ECDH
- Secure Storage: Encrypted data stored in FlutterSecureStorage
- Hardware Keys: Master keys protected by Secure Enclave (iOS) or Keystore (Android)
Cryptographic Parameters #
| Parameter | Value | Standard |
|---|---|---|
| Data Encryption | AES-256-GCM | NIST SP 800-38D |
| Key Agreement | ECDH P-256 (secp256r1) | NIST SP 800-56A |
| Key Derivation | HKDF-SHA256 | RFC 5869 |
| GCM Nonce Size | 96 bits (12 bytes) | NIST recommended |
| GCM Tag Size | 128 bits (16 bytes) | NIST recommended |
Design Philosophy #
This package follows a secure-by-default approach:
- No Encryption Options: Users cannot disable or configure encryption
- No Storage Selection: All data goes to secure storage automatically
- Simple API: Hard to misuse - just store and retrieve
- Automatic Key Management: Keys are managed internally using hardware security
- Single Responsibility: This package only does secure storage, nothing else
Security Model & Limitations #
What This Package Protects Against #
- β Data at rest: All stored data is encrypted with AES-256-GCM
- β Key extraction: Master keys are stored in hardware-backed secure storage (Secure Enclave/Keystore)
- β Data tampering: GCM authentication tags detect any modification to encrypted data
- β Key reuse attacks: Each encryption operation uses a fresh Data Encryption Key (DEK)
What This Package Does NOT Protect Against #
- β Rooted/Jailbroken devices: On compromised devices, hardware security guarantees may be bypassed. This package does not detect or block usage on such devices.
- β Runtime memory attacks: Decrypted data exists briefly in memory during retrieval. Memory-scraping malware on a compromised device could potentially access this data.
- β App-level compromise: If your app itself is compromised (e.g., through malicious dependencies or code injection), stored secrets may be exposed.
- β Side-channel attacks: This package does not implement countermeasures against timing attacks or other side-channel vulnerabilities.
- β iOS data persistence: On iOS, Keychain data persists after app uninstall. If your threat model requires data deletion on uninstall, implement additional cleanup logic.
Recommendations #
- Server-side validation: Never rely solely on local secure storage for critical security decisions. Always validate important data server-side.
- Sensitive data minimization: Store only what's necessary. Consider token expiration and rotation strategies.
- Defense in depth: Use this package as one layer of your security strategy, not the only layer.
- User authentication: Consider requiring user authentication (biometrics/PIN) for accessing highly sensitive data.
Best Practices #
- Initialize Once: Call
initialize()once inmain()beforerunApp() - Use Unique Bundle IDs: Use your actual app identifier consistently
- Handle Errors: Always wrap API calls in try-catch blocks
- Check Initialization: Use
isInitializedproperty if needed - Don't Store Large Data: This is designed for secrets, not bulk storage
- Regular Updates: Keep the package and dependencies updated for security patches
- Data Minimization: Only store data that you actually need
- Access Control: Implement proper access controls in your application layer
- Security Testing: Test your app's behavior in various security scenarios
- Graceful Degradation: Design your app to handle storage failures gracefully
Troubleshooting #
iOS Simulator #
- Secure Enclave is not available on simulators
- The plugin falls back to standard Keychain storage
Android Emulator #
- StrongBox is not available on emulators
- The plugin uses software-backed Android Keystore
Data Persistence After App Uninstall #
| Platform | Behavior | Reason |
|---|---|---|
| Android | Data deleted on uninstall | Keystore keys tied to app; removed with app |
| iOS | Data persists after uninstall | Keychain is system-level storage |
Android: When you uninstall and reinstall, all stored data becomes unreadable because the encryption keys are deleted with the app.
iOS: Keychain items persist across app uninstalls by default. Your encrypted data remains accessible after reinstall.
Recommendations:
- Design your app to handle missing data gracefully on both platforms
- For iOS, if you need a "fresh start" on reinstall, consider clearing vault data on first launch
- Don't rely on uninstall to clear sensitive data on iOS
Backup/Restore & Device Migration #
When users restore from cloud backup (Google Backup, iCloud) or migrate to a new device:
| What Transfers | What Does NOT Transfer |
|---|---|
| Encrypted data (stored in secure storage) | Encryption keys (device-bound in Keystore/Keychain) |
Result: Restored data exists but cannot be decrypted β "orphaned data"
Built-in Solution: The vault automatically detects and handles this:
// Default behavior: automatically clear orphaned data
await vault.initialize(bundleId: 'com.example.app');
// Optional: disable auto-clear if you have custom recovery logic
await vault.initialize(
bundleId: 'com.example.app',
clearOnKeyMismatch: false, // NOT recommended unless you handle it yourself
);
// Safe to call concurrently - only one initialization will run
await Future.wait([
vault.initialize(bundleId: 'com.example.app'),
vault.initialize(bundleId: 'com.example.app'),
]); // β
No race condition
How it works:
- On first
initialize(), the vault stores a validation "canary" value - On subsequent launches, it tries to decrypt the canary
- If decryption fails (keys changed), it clears ALL orphaned data
- Fresh data can then be stored with the new device's keys
Important: Data stored before backup/restore will be lost. This is by design - the keys are device-bound for security. Plan your app's data strategy accordingly (e.g., re-fetch tokens from server after restore).
Important Notes for Production Use #
Security Disclaimer #
β οΈ Important: While RSPL Secure Vault implements industry-standard encryption (AES-256-GCM) and follows security best practices, no software can guarantee 100% security. Always conduct your own security audits and compliance reviews before using in production applications, especially those handling sensitive data.
Security Considerations #
| Consideration | Description |
|---|---|
| Audit Required | Perform independent security audits for applications handling sensitive data |
| Compliance | Verify that your implementation meets your specific regulatory requirements |
| Key Management | The security of your data depends on the platform's secure storage implementation |
| Testing | Thoroughly test encryption/decryption flows in your specific use case |
| Root/Jailbreak | This package does not include root/jailbreak detection - consider adding RASP tools if needed |
Legal & Compliance #
| Area | Your Responsibility |
|---|---|
| Compliance | You are responsible for ensuring compliance with applicable laws and regulations |
| Data Protection | Review data protection requirements for your jurisdiction and industry |
| User Consent | Ensure proper user consent for data collection and storage |
| Backup Strategy | Implement appropriate backup and recovery procedures for your use case |
| Data Retention | Define and enforce data retention policies |
Recommendation for Mission-Critical Applications #
For mission-critical applications handling highly sensitive data (financial, healthcare, etc.), consider additional security measures:
- Certificate Pinning: Protect API communications
- Runtime Application Self-Protection (RASP): Detect and respond to runtime threats
- Regular Penetration Testing: Engage third-party security firms
- Biometric Authentication: Add extra layer for sensitive operations
- Obfuscation: Protect your app code from reverse engineering
Compliance & Standards #
RSPL Secure Vault is designed to help meet common regulatory requirements by providing a secure foundation:
Healthcare (HIPAA) #
| Requirement | How This Package Helps |
|---|---|
| Encryption of PHI at rest | β AES-256-GCM encryption for all sensitive data |
| Key management | β Platform keychain/keystore for encryption keys |
| Access controls | πΈ Implement app-level controls |
| Audit trails | πΈ Implement app-level logging |
Financial (PCI DSS) #
| Requirement | How This Package Helps |
|---|---|
| Strong cryptography | β AES-256-GCM encryption |
| Key management | β Platform secure storage |
| Data protection | β Envelope encryption for payment data |
| Tamper detection | β GCM authentication tags |
Enterprise (SOC 2) #
| Requirement | How This Package Helps |
|---|---|
| Data encryption at rest | β All data encrypted before storage |
| Access controls | πΈ Implement app-level controls |
| Security monitoring | πΈ Implement app-level monitoring |
GDPR #
| Requirement | How This Package Helps |
|---|---|
| Data protection | β Encryption protects personal information |
| Right to deletion | β
clear() and remove() methods |
| Data minimization | πΈ Only store what you need |
| No telemetry | β This package collects no analytics or telemetry |
Legend: β = Provided by this package | πΈ = Implement in your application
β οΈ Important: While RSPL Secure Vault provides security foundations, you are responsible for ensuring your complete application meets regulatory requirements. Conduct security audits and compliance reviews before production deployment.
FAQ #
General Questions #
Q: Is this package production-ready?
A: Yes, but always conduct your own security review for sensitive applications. See our Security Audit for details.
Q: Does this work on web/desktop?
A: No, this package is designed specifically for mobile platforms (iOS/Android) where hardware-backed security is available.
Security Questions #
Q: Can I disable encryption for better performance?
A: No, and this is intentional. The package is designed to be secure by default with no way to accidentally store unencrypted data.
Q: What happens if the device is rooted/jailbroken?
A: Hardware security guarantees may be bypassed on compromised devices. Consider adding RASP (Runtime Application Self-Protection) tools for additional protection.
Q: Are my keys backed up to the cloud?
A: No, encryption keys are stored in the device's Secure Enclave (iOS) or Keystore (Android) and are not included in cloud backups by design.
Technical Questions #
Q: Why does retrieve() return null instead of throwing?
A: null indicates the key doesn't exist, which is a normal condition. Exceptions are reserved for actual errors (initialization failure, decryption failure, etc.).
Q: Can I store binary data?
A: The API accepts strings only. For binary data, encode as Base64 first:
import 'dart:convert';
final bytes = Uint8List.fromList([1, 2, 3]);
await vault.store('binary_data', base64Encode(bytes));
final retrieved = base64Decode(await vault.retrieve('binary_data')!);
Q: Is the initialization thread-safe?
A: Yes, concurrent calls to initialize() are handled safely - only one initialization runs, others wait for it to complete.
Folder Structure #
rspl_secure_vault/
ββ lib/
β ββ rspl_secure_vault.dart # Main package export
β ββ src/
β ββ rspl_secure_vault.dart # Main vault implementation
β ββ common_platform/
β ββ rspl_secure_vault_api.dart # Internal platform API
ββ android/ # Android native implementation
ββ ios/ # iOS native implementation
ββ example/ # Example app
ββ test/ # Unit tests
Example #
For a complete working example, see the example app on GitHub.
Acknowledgments #
- Built with Pigeon for type-safe platform channel communication
- Uses flutter_secure_storage for secure persistence
- Uses CryptoKit on iOS
- Uses Android Keystore on Android
Contributing #
Contributions welcome! Please read:
- CONTRIBUTING.md β setup, branch strategy, commit convention
- CODE_OF_CONDUCT.md
Run checks before push:
dart format .flutter analyzeflutter test
User Privacy Notes #
- This package does not collect any user information or share data with third-party services.
Author, Maintainers & Acknowledgements #
- Developed by Rishabh Software.
- Thanks to the Flutter community for the amazing packages used in this project.
License #
This package is licensed under the Rishabh Software Source Available License (Non-Commercial) V.1.
- β Free for personal projects, learning, academic purposes, and evaluation
- β You may modify and fork for non-commercial use
- β Commercial use requires a separate license
For licensing inquiries, refer to LICENSE for contact details.
Made by Rishabh Software Team #
Contact #
Have questions, suggestions, or feedback? We'd love to hear from you!
π§ Email: opensource@rishabhsoft.com
π Contact Us: https://www.rishabhsoft.com/contact-us