memseal
Encrypt and store secrets in memory with password-based key derivation, authenticated encryption, and automatic zeroization.
Disclaimer: This library has not been independently audited. While it uses well-established cryptographic primitives (XChaCha20-Poly1305, Argon2i, HKDF-SHA256 via orion), the integration has not been reviewed by a third-party security firm. Use at your own risk in production environments. If you find a vulnerability, please see SECURITY.md for responsible disclosure.
Quick Start
use Vault;
let mut vault = create.unwrap;
// Store secrets
vault.store.unwrap;
vault.store.unwrap;
// Export to bytes (for persistence or transmission)
let bytes = vault.export.unwrap;
// Reopen with the same password
let vault = open.unwrap;
let api_key = vault.retrieve.unwrap;
assert_eq!;
API
use ;
use Path;
// Create & open (password must be >= 8 bytes)
let mut vault = create?;
let vault = open?;
// File I/O
vault.save?; // &mut self (rotates nonce)
let vault = load?;
// Store, retrieve, remove
vault.store?; // name max 255B, data max 64 MiB
let data = vault.retrieve?; // Option<Vec<u8>>
let existed = vault.remove?; // bool
// Export to bytes
let bytes = vault.export?; // &mut self (rotates nonce)
// Change password (re-derives keys, re-encrypts all entries one-at-a-time)
vault.change_password?;
Use Cases
- Secret management -- Hold API keys, database credentials, or signing keys in memory without exposing plaintext to swap, core dumps, or memory scanners.
- Encrypted vaults -- Store secrets with authenticated encryption. The index hides entry names behind HMAC and protects KDF parameters against downgrade attacks.
- Key derivation pipelines -- Derive purpose-specific subkeys from a master secret using HKDF with domain separation.
- Credential caches -- Cache decrypted credentials with automatic zeroization on drop. Memory is locked with
mlockto prevent paging to disk.
Threat Model
What memseal protects against
| Threat | Mitigation |
|---|---|
| Memory disclosure via swap | Ciphertext is mlock'd to prevent the OS from paging it to disk. |
| Cold boot / memory dump | Data is encrypted at rest in memory with XChaCha20-Poly1305. Plaintext exists only briefly inside access() callbacks and is zeroized immediately after. |
| Key reuse across operations | Master key is never used directly. Two distinct subkeys (encryption, HMAC) are derived via HKDF-SHA256 with domain-separated info labels and the KDF salt. |
| Nonce reuse | Nonces are derived deterministically from a monotonic counter via HKDF, not generated randomly. Counter overflow is checked. Index nonce rotated on every export. |
| KDF parameter downgrade | The VaultHeader (containing Argon2i params) is passed as AAD to AEAD encryption. Tampering with iterations or memory cost causes authenticated decryption to fail. Header validated against bounds before KDF runs. |
| Entry swap attacks | Each entry's ciphertext is bound to its HMAC'd key and data counter via AAD. Swapping encrypted blobs between entries is detected. |
| Entry name leakage | Index keys are HMAC-SHA256(hmac_key, name), not plaintext. An attacker with access to the serialized index cannot enumerate entry names without the key. |
| Use-after-free of secrets | SecureMemoryVault implements Drop with zeroization of ciphertext, followed by munlock. All temporary key material and plaintext is zeroized on every code path, including errors. |
| Tampered ciphertext | All encryption uses authenticated AEAD (Poly1305 tag). Any bit flip in ciphertext or AAD is detected and rejected. |
| Resource exhaustion via crafted files | Header length, KDF parameters, file size, entry name length, and entry data size are all bounded before processing. |
| Weak passwords | Minimum password length (8 bytes) enforced on vault creation and password change. |
What memseal does NOT protect against
| Threat | Reason |
|---|---|
| Kernel-level attacker | A root/kernel attacker can read process memory regardless of mlock. This is a user-space library. |
| Side-channel attacks | No countermeasures against Spectre, cache timing, or power analysis. The crypto primitives (orion) are constant-time where possible. |
| Compromised dependencies | The library trusts its dependency chain (orion, memsec, zeroize). CI runs cargo audit on every push. |
| Denial of service | An attacker who can write to the vault data can corrupt it. Integrity is detected, but availability is not guaranteed. |
| Debugger-based extraction | A debugger attached to the process can read decrypted data during access() callbacks. Use OS-level protections (prctl(PR_SET_DUMPABLE, 0)) to mitigate. |
Architecture
Password (>= 8 bytes)
|
Argon2i (128 MiB, 4 iterations, random 16B salt)
|
Master Key (32B)
|
HKDF-SHA256 (salt = kdf_salt)
/ \
enc_subkey hmac_subkey
(32B) (32B)
| |
SecureMemoryVault SecureMemoryVault
(XChaCha20-Poly1305 (HMAC-SHA256
streaming AEAD, entry name hashing)
mlock'd memory)
|
derive_nonce(enc_key, counter, salt)
via HKDF with domain separation
|
Per-entry: seal_with_aad(key, nonce, data, hmac_key || counter)
Cryptographic Primitives
| Primitive | Implementation | Purpose |
|---|---|---|
| XChaCha20-Poly1305 (streaming) | orion | In-memory encryption in 4KB chunks |
| XChaCha20-Poly1305 (single-shot) | orion | AAD-protected encryption of vault index and entries |
| HKDF-SHA256 | orion | Subkey derivation (with KDF salt), nonce derivation from counter |
| HMAC-SHA256 | orion | Entry name hashing in vault index |
| Argon2i | orion | Password-based key derivation |
| OsRng | rand_core | Cryptographically secure random generation |
Security Properties
#![deny(unsafe_code)]at crate level. Onlysecure_memory_vault.rsallows unsafe formlock/munlock.- Domain separation for all key derivation:
MEMSEAL_SUBKEY_ENC_v1,MEMSEAL_SUBKEY_HMAC_v1,MEMSEAL_NONCE_CTR_v1,MEMSEAL_DATA_NONCE_v1,MEMSEAL_NAME_NONCE_v1. - Minimal key exposure. Facade-level crypto operations execute inside
SecureMemoryVault::access()callbacks. Note: the internal encryption key and nonce of eachSecureMemoryVaultinstance reside on the heap withoutmlock(only the ciphertext buffer is locked). - Authenticated encryption everywhere. No unauthenticated ciphertext path exists. Per-entry AAD prevents entry-swap attacks.
- Version validation on deserialization of
VaultIndexandVaultHeader. - Bounded input processing. Header length, KDF parameters, file size (256 MiB), entry name (255B), and entry data (64 MiB) are all validated before use.
- Zeroization on all paths. Key material, plaintext, and entry names are zeroized even on error returns.
Building
CI
GitHub Actions runs on every push and PR to main (actions SHA-pinned, stable toolchain):
cargo check-- compilationcargo fmt --check-- formattingcargo clippy -D warnings-- lintscargo test-- 78 tests (74 unit + 4 doctest)cargo audit-- vulnerability scanning
MSRV
Rust 2024 edition. CI runs stable toolchain.
License
MIT -- see LICENSE.