dart_secret
A lightweight wrapper for sensitive string values (auth tokens, API keys,
passwords) that reduces — but does not eliminate — how long a secret's
plaintext remains recoverable from process memory, inspired by Rust's
secrecy/zeroize crates.
Why
Once a secret is read from any storage (including secure storage packages
like flutter_secure_storage), it exists as a plain object in your app's
memory. Dart's String type is immutable, so there is no way to zero it
out when you're done with it — the old value simply waits to be garbage
collected, on no guaranteed schedule.
dart_secret stores the secret as a mutable byte buffer instead, so it
can be explicitly overwritten with zeros when you're done — via
dispose().
Honest limitations (please read)
- This does not guarantee zero memory presence. If you already have
a
Stringbefore wrapping it (e.g. fromflutter_secure_storage.read()), that original Strntouched — Dart gives no public API to zero an existing String's memory.dart_secretcan only guarantee zeroing of the byte buffer it owns internally, from the point you wrap the value onward. - In empirical testing, calling
dispose()measurably reduced the number of recoverable copies of a test secret in a raw memory snapshot compared to simply dropping aStringreference — but did not reduce it to zero. Treat this package as defense-in-depth, not a complete solution. - This is a young, unaudited package. It has not undergone independent security review. Do not treat it as a substitute for OS-level secure storage (Keychain/Keystore) for data at rest — use it alongside such storage, for the in-memory portion of a secret's lifetime.
Usage
import 'package:dart_secret/dart_secret.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
final storage = FlutterSecureStorage();
// Read the secret and wrap it as early as possible.
final rawValue = await storage.read(key: 'auth_token');
final secret = DartSecret(rawValue!);
// Use it.
final token = secret.reveal();
await apiCall(token);
// When done, dispose it explicitly.
secret.dispose();
API
DartSecret(String value)— wrap an existing secret string.DartSecret.fromBytes(Uint8List bytes)— wrap raw bytes directly, skipping String creation if your source can already give you bytes..reveal()→String— get the secret as a string (creates a new, untracked copy; use immediately)..revealBytes()→Uint8List— get the secret as raw bytes..dispose()— overwrite the internal buffer with zeros..isDisposed→bool.
License
MIT