Expand description
§secrets-vault
AES-256-GCM encrypted key-value vault. QVLT v2 encrypts every entry under
its own HKDF-derived key, so reading one secret decrypts exactly one record —
never the whole vault. The legacy v1 single-blob format is still readable
(migration only). Full format spec: QVLT2_SPEC.md.
§Quick Start
use secrets_vault::Vault;
// Create or load a vault
let mut vault = Vault::new();
vault.set("API_KEY", "sk-secret-123");
vault.set("DB_URL", "postgres://localhost/mydb");
// Encrypt and save
let bytes = vault.encrypt("my-passphrase")?;
std::fs::write("vault.qvlt", &bytes)?;
// Load and decrypt
let data = std::fs::read("vault.qvlt")?;
let vault = Vault::decrypt(&data, "my-passphrase")?;
assert_eq!(vault.get("API_KEY"), Some("sk-secret-123"));§Vault File Format (QVLT v2 — see QVLT2_SPEC.md §4)
[4] Magic "QVLT" [1] Version 0x02 [1] KDF id [2] Flags (0)
[16] Vault salt (stable across saves) [4] u32 entry count
per entry (sorted by name): [1] scheme [2] name len [N] name
[12] nonce [16] tag [4] ct len [C] ct
[32] Manifest MAC = HMAC-SHA256(mac_key, file[..len-32])v1 (version 0x01) is a single blob: salt + nonce + tag + one ciphertext of all entries. Kept read-only for migration.
§Security
- AES-256-GCM authenticated encryption per entry; AAD binds version‖scheme‖name
- PBKDF2-HMAC-SHA256 @ 600,000 iterations once per open → HKDF-SHA256 per entry
- Values padded to 32-byte buckets (true length inside the authenticated plaintext)
- Whole-file HMAC-SHA256 manifest (constant-time verify) — deletion/reorder/rollback-within-file detection
- Plaintext and key material zeroized after use
Structs§
- Master
Secret - The vault-level KDF output. Derive ONCE per open (the expensive step), then zeroize the passphrase — every other key HKDF-derives from this (spec §4.3). Carries the salt it was derived under so entry/mac/registry keys need no extra context.
- Vault
- An in-memory key-value store that can be encrypted to/from the QVLT format.
- Vault
Reader - A validated v2 vault image supporting selective decryption and no-read
splicing (spec §5). Holds the raw bytes; values stay ciphertext until
Self::decrypt_oneis called for a specific name.
Enums§
- Vault
Error - Errors that can occur during vault operations.
Constants§
- ITERATIONS
- PBKDF2 iteration count (OWASP 2023 recommendation for SHA-256).
- KDF_
PBKD F2 - KDF id: PBKDF2-HMAC-SHA256 @
ITERATIONS. - MAC_LEN
- Trailing manifest MAC length (HMAC-SHA256).
- MAX_
KEY_ LEN - Maximum key name length in bytes.
- MAX_
NAME_ LEN - Max storage-name length: project(256) + ‘/’(1) + key(256).
- MAX_
VALUE_ LEN - Maximum value length in bytes.
- PAD_
BLOCK - Value padding bucket (spec §4.2): plaintext bodies are 4-byte true-length prefix + value + zero pad, rounded up to a multiple of this.
- SALT_
LEN - Vault-level KDF salt length (v1 and v2).
- SCHEME_
HKDF - Entry key scheme: HKDF from the master secret.
- V2_
HEADER_ LEN - v2 header: magic(4) + version(1) + kdf(1) + flags(2) + salt(16) + count(4).
- V2_
VERSION - v2 version byte.
Functions§
- decrypt_
blob - Decrypt a QVLT container produced by
encrypt_blob. Returns the plaintext bytes (caller zeroizes after use). - decrypt_
raw_ blob - encrypt_
blob - Encrypt arbitrary bytes into the same QVLT container the vault uses (AES-256-GCM,
PBKDF2-SHA256 @ 600k, fresh salt+nonce). Lets other on-disk artifacts (e.g. the
scoped registry) reuse the audited crypto core without touching
Vault. - encrypt_
raw_ blob - is_v1
- True if the bytes look like a legacy v1 file.
- is_v2
- True if the bytes look like a QVLT v2 file (magic + version only — full
validation happens in
VaultReader::open). - is_
valid_ key - Check if a key name is valid: non-empty, ≤256 bytes, and
[A-Za-z0-9_-]only. This is exactly Google Secret Manager’s allowed secret-ID charset (hyphens are valid there; dots are NOT), so a key that validates here is storable in every backend — keychain account, HashMap key, and GSM secret ID alike. Hyphens matter in practice: many real-world secret names are kebab-case (e.g.prod-db-password). - is_
valid_ project - Check a project namespace is safe to use as a vault-key prefix: non-empty,
≤256 bytes, and
[A-Za-z0-9_.-]only — crucially NO slash, soproject/KEYhas exactly one separator and can’t be traversal/injection-abused. - is_
valid_ storage_ name - A storage name is either
KEYorproject/KEY(spec §4.1) — at most one/. - parse_
env_ lines - Parse KEY=VALUE lines (with optional
exportprefix and quote stripping). - random_
bytes - Generate
ncryptographically secure random bytes from the OS CSPRNG. Used bysecrets genso a fresh credential never has to be printed. - random_
salt - Fresh random vault salt (v2: generated at vault creation /
rekeyonly — stable across ordinary saves so splicing needs no re-encryption). - v2_
create - Build a fresh v2 file from scratch (empty vault creation, migration,
rekey).
entriesneed not be sorted; names are validated. - v2_salt
- Parse a v2 header far enough to return the vault salt — needed BEFORE key derivation (chicken/egg: the master secret derives from this salt). Also enforces the fail-closed prefix checks: magic, version, KDF id, zero flags.