Skip to main content

Crate secrets_vault

Crate secrets_vault 

Source
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§

MasterSecret
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.
VaultReader
A validated v2 vault image supporting selective decryption and no-read splicing (spec §5). Holds the raw bytes; values stay ciphertext until Self::decrypt_one is called for a specific name.

Enums§

VaultError
Errors that can occur during vault operations.

Constants§

ITERATIONS
PBKDF2 iteration count (OWASP 2023 recommendation for SHA-256).
KDF_PBKDF2
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, so project/KEY has exactly one separator and can’t be traversal/injection-abused.
is_valid_storage_name
A storage name is either KEY or project/KEY (spec §4.1) — at most one /.
parse_env_lines
Parse KEY=VALUE lines (with optional export prefix and quote stripping).
random_bytes
Generate n cryptographically secure random bytes from the OS CSPRNG. Used by secrets gen so a fresh credential never has to be printed.
random_salt
Fresh random vault salt (v2: generated at vault creation / rekey only — stable across ordinary saves so splicing needs no re-encryption).
v2_create
Build a fresh v2 file from scratch (empty vault creation, migration, rekey). entries need 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.