secure_vault
Fast, encrypted key-value storage for Flutter. Your data is encrypted with
AES-256-GCM and its key lives in the iOS/macOS Keychain or the Android Keystore,
just like flutter_secure_storage, but reads are synchronous and writes are
an order of magnitude faster.
final vault = await SecureVault.open('session');
await vault.setString('token', 'eyJhbGciOi...');
final token = vault.getString('token'); // synchronous, no await
Why
flutter_secure_storage sends every read and write over a platform channel
to the keychain, which is slow and async-only. It also isn't built for more
than a handful of small values. shared_preferences is fast but stores
everything in plaintext.
secure_vault keeps only one thing in the keychain: a random 256-bit key.
Everything else lives in an encrypted file that is loaded into memory once.
| 200 writes + 200 reads (iOS simulator, debug build) | Writes | Reads |
|---|---|---|
| secure_vault | 72 ms | 0.1 ms |
| flutter_secure_storage | 706 ms | 106 ms |
Run the benchmark yourself in the example/ app.
Features
- Synchronous reads after a single
open(). - Typed API:
String,int,double,bool,List<String>, JSON maps and lists, and rawUint8Listbytes. - Reactive:
vault.changesstreams every change, andvault.watch('key')streams one key's value. - Crash safe: writes are appended and fsync'd. A record torn by a crash is dropped on the next open, and compaction swaps files atomically.
- Tamper evident: every record is authenticated. A modified, truncated or swapped file is detected, never silently misread.
- Batch writes:
setAllwrites many values with one disk flush. - Bring your own key: pass
encryptionKey(for example derived from a passphrase) or implementVaultKeyStore. - Multiple vaults: each name is a separate file with its own key.
- Android, iOS, macOS, Windows and Linux.
Usage
import 'package:secure_vault/secure_vault.dart';
final vault = await SecureVault.open('settings');
// write: memory updates immediately, the future completes once on disk
await vault.setBool('onboarded', true);
await vault.setMap('profile', {'name': 'Ada', 'plan': 'pro'});
await vault.setBytes('private_key', keyBytes);
await vault.setAll({'theme': 'dark', 'fontScale': 1.2});
// read
vault.getBool('onboarded'); // true
vault.getMap('profile')?['plan']; // 'pro'
vault.get<String>('theme'); // 'dark'
vault.containsKey('token'); // false
// remove
await vault.remove('theme');
await vault.clear();
// observe
vault.watch<bool>('onboarded').listen((value) => print(value));
// delete the file and its key entirely, for example on logout
await SecureVault.destroy('settings');
Opening options
await SecureVault.open(
'session',
keyStore: const SecureStorageKeyStore(), // where the key lives (default)
encryptionKey: null, // or a 32-byte key of your own
directory: null, // defaults to app support dir
onCorruption: VaultCorruptionPolicy.reset,
);
onCorruption decides what happens when the file can't be decrypted:
reset(the default) wipes the unreadable data and starts empty.throwErrorthrows aVaultExceptionand leaves the file alone.
It applies when the key is gone (VaultErrorCode.missingKey) or when
authentication fails (VaultErrorCode.corrupted).
Security model
- Each vault has its own random 256-bit key, made with
Random.secure().SecureStorageKeyStorestores it withfirst_unlock_this_deviceaccessibility on Apple platforms. That means it never syncs to iCloud and stays readable by background tasks after the first unlock. - Every record is encrypted with AES-256-GCM and a random 96-bit nonce. The vault name is bound in as associated data, so records can't be moved between vaults.
- The key is device-bound, so a vault file restored onto another device
can't be read. You'll get
missingKey, which theresetpolicy handles by starting fresh. Treat vault contents like a cache of secrets, not the only copy of important data. - Decrypted values live in memory while the vault is open, like any in-memory cache. This protects data at rest. It does not protect against code running inside your app or on a compromised, rooted device.
Notes
- Everything is held in memory, so this is built for settings, tokens, credentials and small records. Up to a few megabytes is fine. It is not a replacement for a database of large blobs.
- Vaults above 64 KB decrypt on a background isolate when opened, so large vaults don't stall the first frame.
- Web isn't supported, because the browser has no secure place for the key.
Libraries
- secure_vault
- Fast, encrypted key-value storage for Flutter.