envault 0.1.0
envault: ^0.1.0 copied to clipboard
Fintech-grade secret management for Flutter/Dart. AES-256-GCM encryption in generated code, SecureString type, build-time validation, and git hygiene enforcement. The only Dart package with a derived [...]
envault #
If you decompile a Flutter app built with envied or flutter_dotenv today, you can extract its API keys in about 60 seconds using the strings command or a basic XOR reversal script.
envault was built to fix this. It’s a secret management package that uses actual cryptography (AES-256-GCM) instead of obfuscation, ensuring your keys are never stored in your binary in any reversible form.
Why existing solutions fail at scale #
flutter_dotenv: Ships your.envfile as a plaintext asset inside the APK/IPA. This isn't security; it's a configuration file.envied: Bakes secrets into your Dart code using XOR obfuscation. XOR is a toy cipher. It’s trivial to extract the key and ciphertext via static analysis. Furthermore, it relies onbuild_runner, which severely degrades build times on large projects with lots of Freezed/JSON-serializable models.flutter_secure_storage: Great for storing user tokens at runtime, but useless for compile-time secrets (like Stripe publishable keys or Maps API keys) because hardware keystores can't be pre-seeded at compile time.
How envault works #
envault shifts the paradigm from "obfuscate the string" to "encrypt the string and derive the key at runtime."
- Standalone CLI (Zero
build_runner): You runenvault generate. It parses your.env, encrypts the values, and spits out a single Dart file. It doesn't touch your 150 Freezed models. - AES-256-GCM: Every secret gets its own 96-bit CSPRNG IV and is encrypted using NIST-standard AES-GCM.
- Derived Keys, Not Stored Keys: The master key is never stored in the binary. It’s derived at runtime via PBKDF2 (310,000 iterations).
- Hardware Acceleration: We delegate decryption to the OS (
java.securityon Android,CryptoKiton iOS 13+) viacryptography_flutter, preventing timing side-channel attacks and avoiding UI thread blocks. - Memory Safety (
SecureString): Decrypted secrets are accessed via a scoped.use()callback. Once the closure finishes, the string reference is dropped, making it instantly eligible for Dart's Garbage Collector.
Installation #
# pubspec.yaml
dependencies:
envault: ^0.1.0
cryptography_flutter: ^2.3.4 # Required for hardware-accelerated decryption
Install the generator globally:
dart pub global activate envault_cli
Usage #
- Initialize hardware crypto in your
main.dart:
import 'package:cryptography_flutter/cryptography_flutter.dart';
void main() {
FlutterCryptography.enable(); // Ensures we use OS-level crypto APIs
runApp(const MyApp());
}
- Define your vault in
vault.dart:
import 'package:envault/envault.dart';
part 'vault.g.dart';
@VaultEnv(path: '.env')
abstract class Vault {
@VaultField(varName: 'API_KEY')
static SecureString get apiKey => _Vault.apiKey;
}
- Generate the code:
envault generate
(Note: If your .env isn't in your .gitignore, the CLI will aggressively warn you or fail the build depending on your strictness settings).
- Access securely: Because decryption involves a 310k-iteration PBKDF2 key derivation (cached after the first run) and AES math, accessing a secret is asynchronous. This physically prevents frame drops on the UI thread.
// The .use() method guarantees the plaintext string doesn't leak into logs.
// Calling print(Vault.apiKey) just prints '[REDACTED:SecureString]'.
final headers = {
'Authorization': await Vault.apiKey.use((key) => 'Bearer $key')
};
CI/CD Validation #
Before committing, run:
envault validate
This checks your .env against Shannon entropy thresholds and regex heuristics to ensure you aren't accidentally checking in placeholder values like YOUR_API_KEY_HERE.
Limitations & Threat Model #
What this protects against:
- Static analysis (
strings app.apk,apktool). - Log leakage (Crashlytics, Sentry, Datadog).
- Accidental GitHub commits of
.envfiles.
What this does NOT protect against:
- Arbitrary Memory Reads (Frida/Rooted devices): If an attacker roots the device and hooks the Dart VM, they can read the heap. Dart strings are immutable and cannot be manually zeroed via C-style
memset. If you need absolute zero-trust memory wiping, you need an FFI-based RASP (Runtime Application Self-Protection) solution.envaultsecures the resting state and greatly reduces the memory attack surface, but it is bound by the laws of the Dart VM.
For full PCI-DSS and OWASP MASVS mappings, see COMPLIANCE.md.