memseal
A small password-based encrypted vault for named secrets, with authenticated encryption, bounded parsing, and explicit memory-hygiene trade-offs.
Status:
memsealis an experimental0.xcrate and has not been independently audited.
Note: This crate is not a wrapper around Linux
mseal(2). "memseal" refers to sealing secrets in an encrypted in-memory vault.
Overview
memseal stores named secrets in an encrypted vault protected by a password.
It is designed for applications that need a small, self-contained encrypted vault that can be kept in memory, exported to bytes, or saved to disk.
It is not a replacement for OS keyrings, HSMs, cloud secret managers, or mature password managers.
Quick Start
use Vault;
let mut vault = create.unwrap;
// Store secrets
vault.store.unwrap;
vault.store.unwrap;
// Export to bytes
let bytes = vault.export.unwrap;
// Reopen with the same password
let vault = open.unwrap;
let api_key = vault.retrieve.unwrap;
assert_eq!;
File Persistence
use Vault;
use Path;
let mut vault = create?;
vault.store?;
// Save to disk
vault.save?;
// Later: load and retrieve
let vault = load?;
let api_key = vault.retrieve?;
# Ok::
API
use ;
use Path;
// Create & open
let mut vault = create?;
let vault = open?;
// File I/O
vault.save?;
let vault = load?;
// Store, retrieve, remove
vault.store?;
let data = vault.retrieve?; // Option<Vec<u8>>
let existed = vault.remove?; // bool
// Export to bytes
let bytes = vault.export?;
// Change password
vault.change_password?;
Limits
| Item | Limit |
|---|---|
| Password length | Minimum 8 bytes |
| Entry name | Maximum 255 bytes |
| Entry data | Maximum 64 MiB |
| Vault file | Maximum 256 MiB |
| Index entries | Maximum 1024 |
Handling Plaintext
retrieve() returns decrypted data as Option<Vec<u8>>.
This is convenient, but it means the caller owns the returned plaintext and is responsible for handling it carefully.
In particular, caller code should avoid:
- logging returned secrets;
- cloning or converting them unnecessarily;
- keeping plaintext alive longer than needed;
- assuming returned plaintext is protected by
mlock.
Internal temporary plaintext and key material are zeroized where possible, but returned plaintext belongs to the caller.
If the caller wants drop-time zeroization, the returned Vec<u8> can be wrapped by the caller using zeroize::Zeroizing:
use Zeroizing;
if let Some = vault.retrieve?
# Ok::
This only zeroizes that returned allocation on drop. It does not prevent accidental copies made by caller code or by the allocator/runtime.
What memseal is
- A small embedded vault for named secrets.
- Password-based: vault keys are derived from a caller-provided password.
- Self-contained: vaults can be exported to bytes or saved to disk.
- Authenticated: encrypted data is protected against tampering.
- Explicit about its limitations and memory-hygiene trade-offs.
What memseal is not
- Not independently audited.
- Not an OS keyring wrapper.
- Not a cloud secret manager.
- Not an HSM.
- Not a password manager.
- Not a general secure-memory allocator.
- Not related to Linux
mseal(2).
Intended Use Cases
- Embedded encrypted vaults — Store named secrets in an application-managed encrypted vault.
- Portable secret bundles — Export/load a password-protected vault without relying on an OS credential store.
- Credential caches — Keep secrets encrypted at rest in memory and on disk, while accepting explicit caller-owned plaintext boundaries.
- Application-managed secret storage — Store small sets of API keys, tokens, or credentials where a lightweight Rust-native vault is appropriate.
Threat Model
Intended mitigations
| Threat | Mitigation |
|---|---|
| Tampered vault data | Vault metadata, index data, and entries are protected with authenticated encryption. Bit flips or modified ciphertext are detected. |
| Entry swap attacks | Each entry ciphertext is bound to its HMAC-derived entry key and data counter through AAD. Swapping encrypted blobs between entries is detected. |
| Entry name leakage in serialized vaults | Entry names are not stored in plaintext. Index keys are derived with HMAC-SHA256. |
| KDF parameter downgrade | The vault header is authenticated as AAD. Tampering with KDF parameters causes decryption to fail. Header values are also bounded before KDF execution. |
| Nonce reuse | Nonces are derived from monotonic counters with domain separation. Counter overflow is checked. The index nonce is rotated on each export. |
| Key reuse across roles | The password-derived master key is not used directly for encryption. Separate encryption and HMAC subkeys are derived with HKDF-SHA256. |
| Plaintext lifetime inside the library | Internal temporary plaintext and key material are zeroized where possible, including error paths. |
| Resource exhaustion from crafted files | Vault file size, header length, KDF parameters, entry name length, and entry data size are bounded before processing. |
| Swap exposure of ciphertext buffers | Internal ciphertext buffers in SecureMemoryVault are locked with mlock via memsec where supported. |
Out of scope / limitations
| Threat | Reason |
|---|---|
| Kernel-level or root attacker | A privileged attacker can read process memory regardless of user-space protections. |
| Debugger-based extraction | A debugger attached to the process can read decrypted data while it is being processed or after it has been returned by retrieve(). |
| Caller-owned plaintext leaks | retrieve() returns Vec<u8>. The caller is responsible for avoiding logs, copies, long-lived plaintext, and unsafe conversions. |
| Side-channel attacks | memseal does not attempt to mitigate Spectre, cache timing, power analysis, or other side channels. |
| Compromised dependencies | The crate trusts its dependency chain, including orion, memsec, and zeroize. |
| Denial of service | Corruption is detected, but availability is not guaranteed if an attacker can modify or delete vault data. |
| Full swap protection | Only internal ciphertext buffers are locked. Internal keys, nonces, allocator metadata, returned plaintext, and caller-owned copies are outside that guarantee. |
| Formal cryptographic assurance | The crate has not been independently audited. The integration layer should be reviewed before high-risk use. |
Architecture
Password (>= 8 bytes)
|
Argon2i
128 MiB, 4 iterations
random 16-byte salt
|
Master Key (32B)
|
HKDF-SHA256
salt = KDF salt
/ \
enc_subkey hmac_subkey
(32B) (32B)
| |
| +--> HMAC-SHA256 entry-name hashing
|
+--> XChaCha20-Poly1305 encryption
Per-entry encryption:
nonce = HKDF(enc_subkey, counter, domain)
aad = HMAC'd entry key || data counter
ct = XChaCha20-Poly1305(key, nonce, plaintext, aad)
Index encryption:
index nonce rotates on every export
vault header is authenticated as AAD
Note: mlock is applied only to internal ciphertext buffers inside SecureMemoryVault. It does not lock every secret-related allocation.
Cryptographic Primitives
| Primitive | Implementation | Purpose |
|---|---|---|
| Argon2i | orion |
Password-based key derivation |
| HKDF-SHA256 | orion |
Subkey derivation and nonce derivation |
| XChaCha20-Poly1305 | orion |
Authenticated encryption |
| HMAC-SHA256 | orion |
Entry-name hashing |
| OsRng | rand_core |
Random salt generation |
mlock / munlock |
memsec |
Best-effort locking of internal ciphertext buffers |
| Zeroization | zeroize |
Clearing internal temporary secrets where possible |
Security Properties
- Small public API. The crate exposes
VaultandVaultError; internal modules are private. - Unsafe code is isolated. The crate uses
#![deny(unsafe_code)]at the crate root. The memory-locking module explicitly allows unsafe code formlock/munlockandunsafe impl Send/Sync. - Domain separation. Key derivation and nonce derivation use distinct domain labels.
- Authenticated encryption. Vault index data and entries are encrypted with AEAD.
- Bounded parsing. Untrusted vault data is checked against size and parameter bounds before processing.
- Atomic file writes.
save()writes to a temporary file, fsyncs it, renames it, and uses0600permissions on Unix. - Best-effort memory hygiene. Internal temporary key material and plaintext are zeroized where possible.
- Partial swap protection. Internal ciphertext buffers are locked with
mlockwhere supported, but this does not cover every allocation.
Comparison
memseal is not a replacement for lower-level memory-hygiene crates such as zeroize, secrecy, or memsec.
It is also not an OS credential-store wrapper like keyring.
memseal is useful when an application wants a small, portable, self-contained encrypted vault for named secrets that it can export, save, and load directly.
Development
Run benchmarks with:
CI
GitHub Actions runs on every push and PR to main:
cargo checkcargo fmt --checkcargo clippy -- -D warningscargo testcargo audit
MSRV
memseal currently targets the Rust stable toolchain and uses the Rust 2024 edition.
Security
This crate has not been independently audited.
Please report security issues privately. See SECURITY.md.
License
MIT — see LICENSE.