Redoubt is a Rust library for storing secrets in memory. Encrypted at rest, zeroized on drop, accessible only when you need them.
Features
- โจ Zero boilerplate โ One macro, full protection
- ๐ Ephemeral decryption โ Secrets live encrypted, exist in plaintext only for the duration of access
- ๐ No surprises โ Allocation-free decryption with explicit zeroization on every path
- ๐งน Automatic zeroization โ Memory is wiped when secrets go out of scope
- โก Amazingly fast โ Powered by AEGIS-128L encryption, bit-level encoding, and decrypt-only-what-you-need
- ๐ก๏ธ OS-level protection โ Memory locking and protection against dumps
- ๐ฏ Field-level access โ Decrypt only the field you need, not the entire struct
- ๐ฆ
no_stdcompatible โ Works in embedded and WASI environments
Installation
Or in your Cargo.toml:
[]
= { = "0.1.0-rc.2", = ["full"] }
Quick Start
use ;
use RedoubtCodec;
use RedoubtSecret;
use cipherbox;
use RedoubtZero;
API
open / open_mut
Access the entire decrypted struct. Re-encrypts when the closure returns:
wallet.open?;
wallet.open_mut?;
open_<field> / open_<field>_mut
Access individual fields without decrypting the entire struct:
wallet.open_seed?;
wallet.open_seed_mut?;
leak_<field>
Get a field value outside the closure. Returns a ZeroizingGuard that wipes memory on drop:
let seed = wallet.leak_seed?;
// use seed...
// seed is zeroized when dropped
Returning values
Closures can return values. The return value is wrapped in a ZeroizingGuard that wipes memory on drop:
let counter = wallet.open_counter_mut?; // Returns Result<ZeroizingGuard<u64>, CipherBoxError>
// counter is zeroized when dropped
Types
Redoubt provides secure containers for different use cases:
use ;
use RedoubtSecret;
// Fixed-size arrays (automatically zeroized on drop)
let mut api_key = new;
let mut signing_key = new;
// Dynamic collections (zeroized on realloc and drop)
let mut tokens = new;
let mut password = new;
// Primitives wrapped in Secret
let mut counter = from;
let mut timestamp = from;
When to use each type
RedoubtArray<T, N>: Fixed-size sensitive data (keys, hashes, seeds). Size known at compile time.RedoubtVec<T>: Variable-length byte arrays that may grow (encrypted tokens, variable-size keys).RedoubtString: Variable-length UTF-8 strings (passwords, mnemonics, API keys).RedoubtSecret<T>: Primitive types (u64, i32, bool) that need protection. Prevents accidental copies via controlled access.
โ ๏ธ Critical: CipherBox fields MUST come from these types
All sensitive data in #[cipherbox] structs MUST ultimately come from: RedoubtArray, RedoubtVec, RedoubtString, or RedoubtSecret.
These types were forensically validated (see forensics/README.md) to leave no traces during the encryption-at-rest workflow. You can compose them into nested structures, but the leaf values containing sensitive data must be these types. Using standard types (Vec<u8>, String, [u8; 32], u64) would leave unzeroized copies during encoding/decoding, defeating the security guarantees.
How they prevent traces
RedoubtVec / RedoubtString: Pre-zeroize old allocation before reallocation
- When capacity is exceeded, performs a safe 3-step reallocation:
- Copy data to temporary buffer
- Zeroize old allocation completely
- Allocate new buffer with 2x capacity and copy from temp (zeroizing temp)
- Safe methods:
extend_from_mut_slice,extend_from_mut_string(zeroize source) - ~40% performance penalty for guaranteed security (double allocation during growth)
- Without this, standard
Vec/Stringleave copies in abandoned allocations
RedoubtArray: Prevents copies during assignment
- Simply redeclaring arrays (
let arr2 = arr1;) can leave copies on stack replace_from_mut_arrayusesptr::swap_nonoverlappingto exchange contents without intermediate copies- Zeroizes the source after swap, ensuring no plaintext remains
RedoubtSecret: Prevents accidental dereferencing of Copy types
- Forces explicit
as_ref()/as_mut()calls to access the inner value - Critical for primitives like
u64which implementCopyand could silently duplicate if accessed directly
Protections
- All types implement
Debugwith REDACTED output (no accidental leaks in logs) - No
CopyorClonetraits (prevents unintended copies of sensitive data) - Automatic zeroization on drop
Security
- Encryption at rest: Sensitive data uses AEAD encryption (AEGIS-128L)
- Guaranteed zeroization: Memory is wiped using compiler barriers that prevent optimization
- OS-level protections: On Linux, the master key lives in a memory page protected by
prctlandmlock, inaccessible to non-root memory dumps - Field-level encryption: Decrypt only what you need, minimizing exposure time
Testing
CipherBox generates failure injection methods for testing error handling:
// In tests:
let mut wallet = new;
wallet.set_failure_mode;
assert!; // 1st succeeds
assert!; // 2nd fails
- In the same crate, test utilities are always available under
#[cfg(test)] - For external crates, use
testing_featureto export them conditionally
See examples/wallet/tests for a complete example.
Platform support
| Platform | Protection level |
|---|---|
| Linux | Full (prctl, rlimit, mlock, mprotect) |
| macOS | Partial (mlock, mprotect) |
| Windows | Encryption only |
| WASI | Encryption only |
no_std |
Encryption only |
Project Insights
For detailed information about testing methodology and other interesting technical details, see INSIGHTS.md.
Benchmarks
To run benchmarks:
License
This project is licensed under the GNU General Public License v3.0-only.