vault_storage 2.1.2
vault_storage: ^2.1.2 copied to clipboard
A package for secure key-value and file storage using Hive and flutter_secure_storage.
Vault Storage #
A secure, fast, and simple local storage solution for Flutter. Built on Hive and Flutter Secure Storage with AES-GCM encryption. Provides key-value storage and encrypted file storage with full web compatibility. Heavy crypto/JSON/base64 work runs in background isolates to keep your UI smooth.
Features #
- Simple API: Intuitive methods with clear intent (saveSecure/saveNormal/get/delete/clearβ¦)
- Smart lookups: get() checks normal first, then secure for performance, or constrain via isSecure
- List stored keys: keys() returns existing keys (optionally filter secure/normal and include file keys)
- Encrypted file storage: Secure (AES-GCM 256-bit) and normal file storage, unified API across platforms
- Web compatible: Native file system on devices; web stores bytes in Hive and auto-downloads on retrieval
- Fast by default: Crypto, JSON (large), and base64 (large) are offloaded to isolates
- Large-file streaming: Secure file encryption supports chunked streaming to reduce memory pressure
- Configurable performance: Tweak isolate thresholds via VaultStorageConfig
- Framework agnostic: Works with any state management or none
Use Cases #
π Built for apps that actually matter. Whether you're building the next unicorn or just want your users' data to be safe, Vault Storage has you covered:
π₯ Healthcare & Medical Apps #
- Patient Records: Keep medical data secure and HIPAA-compliant out of the box
- Medical Imaging: Store diagnostic images offline without compromising security
- Health Tracking: Protect sensitive health data with enterprise-grade encryption
π¦ Financial & Banking Apps #
- Auth Tokens: Store JWT tokens and API keys the right way
- Transaction Data: Keep financial records encrypted and PCI DSS compliant
- Biometric Data: Secure fingerprints and face ID templates safely
- Digital Wallets: Protect cryptocurrency keys and payment info
π Enterprise & Business Apps #
- Corporate Docs: Encrypt contracts and confidential documents
- Employee Data: Manage credentials and personal info securely
- API Keys: Store third-party service credentials safely
- Audit Trails: Maintain encrypted logs for compliance
π± Consumer Apps with Real Users #
- Password Managers: Store encrypted passwords and secure notes
- Messaging Apps: Cache encrypted messages and media files
- Personal Vaults: Secure storage for IDs, certificates, and important docs
- Social Apps: Protect user data and private content
π Cross-Platform Apps #
- Consistent Security: Same protection across mobile, web, and desktop
- Large Files: Handle encrypted images, videos, and documents efficiently
- Offline-First: Secure local storage that works without internet
Why Choose Vault Storage? #
π Modern apps demand modern security. Even simple applications benefit from robust, future-proof storage
Built for Real-World Applications
- π Security by Default: Why worry about data breaches? Get enterprise-grade encryption out of the box
- β‘ Performance First: Smooth UI even with heavy encryption - operations run in background isolates
- π True Cross-Platform: One API that works consistently across mobile, web, and desktop
- π‘οΈ Clear Error Handling: Explicit exceptions with typed StorageError subtypes
Future-Proof Your App
- π Scalable: Start with simple key-value storage, seamlessly add encrypted file storage as you grow
- β Compliance Ready: Already meet GDPR, HIPAA, and PCI DSS requirements without extra work
- π§ Production Ready: Used in real-world applications handling sensitive user data
- π― Pragmatic: Simple, readable API with robust internals
Developer Experience
- π¨ Clean API: Simple, intuitive methods that handle complex security behind the scenes
- π¦ Batteries Included: Error types, utilities, and comprehensive documentation
- π§ͺ Well Tested: 97.5% test coverage gives you confidence in reliability
- π Complete Documentation: Examples, use cases, and troubleshooting guides
Perfect for Both Simple and Complex Apps
Growing App? Start storing user preferences securely, then add document encryption later - same API.
Enterprise App? Get the security and compliance features you need without the complexity.
Consumer App? Your users' data deserves protection, and they'll notice the smooth performance.
π‘ Pro Tip: Even if you're storing "just preferences" today, using proper security from day one prevents costly migrations later when you add user accounts, premium features, or sensitive data.
Important Notes for Production Use #
β οΈ Security Disclaimer: While Vault Storage implements industry-standard encryption (AES-GCM 256-bit) 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 #
- 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
βοΈ Legal & Compliance #
- Your Responsibility: 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
π‘οΈ Best Practices #
- Regular Updates: Keep the package and dependencies updated for security patches
- Error Handling: Implement comprehensive error handling for storage failures
- Data Minimisation: Only store data that you actually need
- Access Control: Implement proper access controls in your application layer
π Recommendation: For mission-critical applications, consider additional security measures such as certificate pinning, runtime application self-protection (RASP), and regular penetration testing.
Getting Started #
Add vault_storage
to your pubspec.yaml
file:
dependencies:
# ... other dependencies
vault_storage: ^2.0.0 # Replace with the latest version
Then, run:
flutter pub get
Migration Guide: 1.x -> 2.0 #
This release simplifies the API and removes the BoxType
-driven/Either-based surface. Key changes and how to migrate:
Why this change? #
- Clarity and intent: Methods like
saveSecure
,saveNormal
, andget(..., isSecure)
are explicit and reduce ambiguity and misuse - Simpler error handling: Throwing typed
StorageError
exceptions simplifies flows compared toEither
-based handling sprinkled across call sites - Less leakage of internals: Removing
BoxType
prevents coupling callers to storage implementation details - Web and files ergonomics: A single key-based file API (with auto-download on web) is easier to use than passing back metadata maps
- Performance and maintainability: A smaller, clearer surface makes it easier to optimize internals (isolates, streaming) and evolve features safely
- Performance and maintainability: A smaller, clearer surface makes it easier to optimise internals (isolates, streaming) and evolve features safely
Trade-offs (considered acceptable):
- Exceptions require
try/catch
instead of.fold()
patterns - Web downloads now use a sensible default filename rather than app-controlled names in this simplified API
- Initialisation and errors
- Before:
await storage.init()
returnedEither
, handled via.fold()
- After:
await storage.init()
throws on failure. Use try/catch.
- Key-value API
- Before:
await storage.set(BoxType.secure, 'k', 'v')
final v = await storage.get<String>(BoxType.secure, 'k')
- After:
await storage.saveSecure(key: 'k', value: 'v')
final v = await storage.get<String>('k', isSecure: true)
- Normal data:
saveNormal(...)
andget(..., isSecure: false)
- Delete:
await storage.delete('k')
(removes from both storages) - Clear:
await storage.clearNormal()
,await storage.clearSecure()
- File storage
- Before:
saveSecureFile(fileBytes, fileExtension)
returned metadata Map you stored and later passed togetSecureFile(metadata, downloadFileName: ...)
- After:
await saveSecureFile(key: 'profile', fileBytes: bytes, originalFileName: 'x.jpg', metadata: {...})
- Retrieve with
await getFile('profile')
(auto-detect secure/normal); optionally constrain viaisSecure
- Delete with
await deleteFile('profile')
- Web: files auto-download on
getFile()
; custom filenames are not configurable in this API
- Error handling
- Before:
Either<StorageError, T>
results - After: methods throw
StorageError
subclasses (StorageReadError
,StorageWriteError
, etc.)
- Performance & internals
- Large JSON/base64 handled via isolates; thresholds configurable in
VaultStorageConfig
- Secure file encryption supports streaming for large files
- More aggressive Hive compaction strategy internally
See CHANGELOG for full details.
Quick Start #
Use the factory to create an instance and initialise once at app start:
import 'package:vault_storage/vault_storage.dart';
final storage = VaultStorage.create();
await storage.init();
// Save values
await storage.saveSecure(key: 'api_key', value: 'my_secret_key');
await storage.saveNormal(key: 'theme', value: 'dark');
// Read values (normal first, then secure)
final token = await storage.get<String>('api_key');
final theme = await storage.get<String>('theme');
// Constrain lookup to secure or normal storage
final secureOnly = await storage.get<String>('api_key', isSecure: true);
final normalOnly = await storage.get<String>('theme', isSecure: false);
The factory returns IVaultStorage
and hides implementation details.
Platform Setup #
This package uses flutter_secure_storage
for secure key management, which requires platform-specific configurations:
Android #
In android/app/build.gradle
, set minimum SDK version to 18 or higher:
android {
defaultConfig {
minSdkVersion 18 // Required for KeyStore
}
}
Note: To prevent backup-related keystore issues, consider disabling auto-backup in your AndroidManifest.xml
:
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:dataExtractionRules="@xml/data_extraction_rules"
...>
iOS #
No additional configuration required. The package uses iOS Keychain by default.
macOS #
Required: Add Keychain access entitlements and configure codesigning to prevent StorageInitializationError
.
1. Create or update entitlement files
macos/Runner/DebugProfile.entitlements
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
</array>
</dict>
</plist>
macos/Runner/Release.entitlements
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
</array>
</dict>
</plist>
2. Configure Codesigning
Ensure proper codesigning is configured in your Xcode project:
- Open
macos/Runner.xcworkspace
in Xcode - Select the Runner project in the navigator
- Go to "Signing & Capabilities" tab
- Ensure:
- Team is selected (required for keychain access)
- Bundle Identifier matches your app's identifier
- Signing Certificate is valid
- Keychain Sharing capability is added (should appear automatically with the entitlements)
3. Clean and rebuild after setup
flutter clean
flutter pub get
flutter run -d macos
4. Troubleshooting macOS Issues
If you still encounter StorageInitializationError
:
- Check Console app for detailed error messages from your app
- Verify codesigning: Run
codesign -dv --verbose=4 /path/to/your/app.app
to verify signatures - Reset Keychain (development only): Delete keychain entries for your app if corrupted
- Check entitlements: Run
codesign -d --entitlements - /path/to/your/app.app
to verify entitlements are applied
Why this is required: VaultStorage
creates secure encryption keys using macOS Keychain during initialization. Without proper entitlements and codesigning, the system denies access to the Keychain, causing the StorageInitializationError
.
Linux #
Install required system dependencies:
sudo apt-get install libsecret-1-dev libjsoncpp-dev
For runtime: libsecret-1-0
and libjsoncpp1
Windows #
No additional configuration required. Note: readAll
and deleteAll
operations have limitations on Windows.
Web #
The package uses WebCrypto on web. Important notes:
- Use HTTPS in production; consider adding Strict-Transport-Security headers
- Data is browser/domain specific and non-portable
- Auto-downloads occur when retrieving files
Recommended header:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Before running your app, you must initialise the service. This is typically done in your main.dart
:
import 'package:flutter/material.dart';
import 'package:vault_storage/vault_storage.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialise the vault storage
final storage = VaultStorage.create();
try {
await storage.init();
runApp(MyApp(storage: storage));
} on StorageError catch (e) {
// Handle initialisation error appropriately
debugPrint('Failed to initialise storage: ${e.message}');
runApp(const ErrorApp());
}
}
class MyApp extends StatelessWidget {
final IVaultStorage storage;
const MyApp({super.key, required this.storage});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: MyHomePage(storage: storage),
);
}
}
// List keys (unique, sorted)
final allKeys = await storage.keys(); // both normal and secure; includes file keys
Usage #
Basic Usage (No Dependencies) #
Use Vault Storage directly without any state management framework:
import 'package:vault_storage/vault_storage.dart';
class StorageManager {
static IVaultStorage? _instance;
static Future<IVaultStorage> get instance async {
if (_instance != null) return _instance!;
final s = VaultStorage.create();
await s.init();
return _instance = s;
}
}
// Usage example
Future<void> example() async {
final storage = await StorageManager.instance;
await storage.saveSecure(key: 'api_key', value: 'my_secret_key');
final value = await storage.get<String>('api_key', isSecure: true);
debugPrint('Retrieved API key: $value');
}
Using with Riverpod #
Create your own provider if you use Riverpod:
dependencies:
vault_storage: ^2.0.0
flutter_riverpod: ^2.5.0
riverpod_annotation: ^2.3.3
dev_dependencies:
build_runner: ^2.4.7
riverpod_generator: ^2.3.9
Then create your own provider file: lib/providers/storage_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:vault_storage/vault_storage.dart';
part 'storage_provider.g.dart';
@Riverpod(keepAlive: true)
Future<IVaultStorage> vaultStorage(VaultStorageRef ref) async {
final implementation = VaultStorage.create();
await implementation.init();
ref.onDispose(() async => implementation.dispose());
return implementation;
}
Don't forget to run code generation:
dart run build_runner build
Then use it in your widgets:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/storage_provider.dart';
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(vaultStorageProvider).when(
data: (storage) => YourWidget(storage: storage),
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('Error: $error'),
);
}
}
And update your main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/storage_provider.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final container = ProviderContainer();
try {
await container.read(vaultStorageProvider.future);
runApp(
UncontrolledProviderScope(
container: container,
child: const MyApp(),
),
);
} catch (error) {
runApp(const ErrorApp());
}
}
Key-Value Storage #
Store and retrieve simple key-value pairs:
// Secure data (encrypted)
await storage.saveSecure(key: 'user_token', value: 'jwt_token_here');
await storage.saveSecure(key: 'user_credentials', value: {
'username': 'john_doe',
'password': 'hashed_password',
});
// Normal data (faster, unencrypted)
await storage.saveNormal(key: 'theme_mode', value: 'dark');
await storage.saveNormal(key: 'language', value: 'en');
// Retrieve data
final token = await storage.get<String>('user_token', isSecure: true);
final theme = await storage.get<String>('theme_mode', isSecure: false);
File Storage (Secure and Normal) #
For images, documents, or any binary data:
import 'dart:typed_data';
Future<void> handleFileStorage(IVaultStorage storage) async {
final Uint8List imageData = /* from picker/network */ Uint8List(0);
// Save a secure file (encrypted)
await storage.saveSecureFile(
key: 'profile_image',
fileBytes: imageData,
originalFileName: 'avatar.jpg',
metadata: {'userId': '123'}, // optional user metadata
);
// Save a normal file (unencrypted)
await storage.saveNormalFile(
key: 'cached_document',
fileBytes: imageData,
originalFileName: 'document.pdf',
);
// Retrieve file bytes by key
final secureBytes = await storage.getFile('profile_image'); // web auto-downloads
final normalBytes = await storage.getFile('cached_document', isSecure: false);
// Delete files
await storage.deleteFile('profile_image');
await storage.deleteFile('cached_document');
}
Note on web: When you call getFile(), the browser automatically downloads the file using a sensible default filename derived from the stored extension. Custom download filenames are not configurable in this simplified API.
Storage Classes Under the Hood #
Internally, Vault Storage maintains separate boxes for normal/secure key-value data and normal/secure files. The public API abstracts this; you only provide keys and optional isSecure where relevant.
Platform-Specific Behaviour #
The package automatically handles platform differences to provide the best user experience:
File Retrieval Behaviour
- Web: Auto-downloads file and returns Uint8List
- Native: Returns Uint8List only (no download)
File Storage Implementation
- Native platforms (iOS, Android, macOS, Windows, Linux): Files are stored in the app's documents directory using the file system
- Web: Files are stored as base64-encoded strings in encrypted Hive boxes (browser storage)
Automatic MIME Type Detection (Web)
For web downloads, MIME types are inferred from file extensions (PDF, images, common docs, audio/video, archives, JSON/TXT, etc.). If unknown, defaults to application/octet-stream
.
No code changes are required - the package handles platform detection and optimisation automatically.
Web Compatibility #
- Native: Files are stored in the app's documents directory
- Web: Files are stored as base64-encoded strings in Hive (secure files encrypted), and auto-download when retrieved
Initialisation in main() #
import 'package:flutter/material.dart';
import 'package:vault_storage/vault_storage.dart';
late final IVaultStorage vaultStorage;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialise storage
vaultStorage = VaultStorage.create();
await vaultStorage.init();
debugPrint('Storage initialised successfully');
runApp(const MyApp());
}
// Use the service anywhere in your app
Future<void> useStorage() async {
await vaultStorage.saveSecure(key: 'api_key', value: 'my_secret_key');
}
Error Handling #
APIs throw typed exceptions that extend StorageError
. Use try/catch:
try {
await vaultStorage.saveSecure(key: 'k', value: 'v');
final v = await vaultStorage.get<String>('k', isSecure: true);
} on StorageInitializationError catch (e) {
debugPrint('Storage not initialised: ${e.message}');
} on StorageReadError catch (e) {
debugPrint('Read failed: ${e.message}');
} on StorageWriteError catch (e) {
debugPrint('Write failed: ${e.message}');
} on StorageSerializationError catch (e) {
debugPrint('Serialization failed: ${e.message}');
}
Storage Management #
// Delete a key from both storages
await vaultStorage.delete('api_key');
// Clear storages
await vaultStorage.clearNormal();
await vaultStorage.clearSecure();
// Inspect keys
final keys = await vaultStorage.keys(includeFiles: true);
// Dispose (e.g., on shutdown)
await vaultStorage.dispose();
Performance Tuning #
Tweak thresholds at startup using VaultStorageConfig
:
// Optional: tune for your workload
VaultStorageConfig.jsonIsolateThreshold = 15000; // chars
VaultStorageConfig.base64IsolateThreshold = 100000; // bytes
VaultStorageConfig.secureFileStreamingThresholdBytes = 1 * 1024 * 1024; // 1MB
Troubleshooting #
Common Initialisation Errors #
"Failed to create/decode secure key"
This error typically occurs when flutter_secure_storage
cannot access the platform's secure storage:
macOS: Ensure keychain access entitlements are properly configured (see Platform Setup above)
Android: Check that minSdkVersion >= 18 and consider disabling auto-backup
Solution: Verify platform-specific setup requirements and restart your app after configuration changes
App Crashes on First Launch
If the app crashes during storage initialisation:
- Check that all platform requirements are met
- Ensure proper error handling in your
main()
function:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final storage = VaultStorage.create();
final initResult = await storage.init();
initResult.fold(
(error) {
print('Storage initialisation failed: ${error.message}');
// Show error screen or fallback UI
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Text('Storage initialisation failed: ${error.message}'),
),
),
));
},
(_) => runApp(MyApp(storage: storage)),
);
}
Web Storage Issues
For web applications:
- Ensure HTTPS is enabled for production
- Check browser compatibility with WebCrypto
- Verify that local storage is not disabled
Debug Mode #
To get more detailed error information, check the console output when initialisation fails. The package provides detailed error messages for different failure scenarios.
Testing #
To run tests:
flutter test
Dependencies #
hive_ce_flutter
: Local storage database ;-flutter_secure_storage
: Secure key storagecryptography_plus
: AES-GCM encryptionweb
: Modern web APIs for web downloads
Platform Support #
- β Android
- β iOS
- β Web
- β macOS
- β Windows
- β Linux
License #
This project is licensed under the MIT License.