memseal
Encrypt and store secrets in memory with password-based key derivation, authenticated encryption, and automatic zeroization.
Note: This crate is not a wrapper around Linux
mseal(2). "memseal" refers to sealing secrets in an encrypted in-memory vault.
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. Internal plaintext is zeroized after use. Note: retrieve() returns plaintext as Vec<u8> — callers are responsible for zeroizing the returned data. |
| 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/munlocksyscalls andunsafe impl Send/Sync(the struct uses aMutexfor interior synchronization and raw pointer fields are only accessed inDrop).- 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. - Partial swap protection. Only the ciphertext buffer inside each
SecureMemoryVaultismlock'd. The internal encryption key and nonce reside on the heap withoutmlock— if swapped to disk, they could be recovered. This is a known trade-off. - Caller-owned plaintext.
retrieve()returnsVec<u8>. Internal temporaries are zeroized, but the returned plaintext is the caller's responsibility to zeroize or limit in scope. - 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 within the library.
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-- unit and doc testscargo audit-- vulnerability scanning
MSRV
Rust 2024 edition. CI runs stable toolchain.
License
MIT -- see LICENSE.