Expand description
§Secure Types
The goal of this crate is to provide a simple way to properly handle sensitive data in memory (e.g. passwords, private keys, etc).
Currently there are 3 types:
SecureString: For working with strings.SecureVec: For working withVec<T>.SecureArray: For working with&[T; LENGTH].
§Features
- Zeroization on Drop: Memory is wiped when dropped.
- Memory locking (OS only): Pages are
mprotectedPROT_NONEexcept during anunlock*scope. That stops accidental reads, including from this process; it is not a defence againstptraceor a debugger. On the malloc path the allocation is also mlocked (Windows:VirtualLock) and, where the OS allows it, excluded from core dumps. Linux usesmemfd_secretwhen the kernel supports it. See How memory is locked. - Scoped access: No
Index/Deref—secret[0]does not compile. Contents are only reachable through theunlock*closures (theexpose-ptrtesting feature aside). Memory is unprotected only for that closure. - Send, not Sync: Values can move to another thread. Sharing one instance needs an explicit lock (
Arc<Mutex<_>>); concurrentunlockraces on page protection. no_std: Zeroization only. Disable the default features — see Feature Flags.- Serde: Optional serialization for
SecureString,SecureVec<T>, andSecureArray<T, LENGTH>. Au8container serializes as a byte buffer; other element types as a sequence (SeqElement). - Binary codec (feature
codec): AserdeSerializer/Deserializer that encodes into locked memory and decodes out of it. No extra dependency beyondserde.
§How memory is locked
- Windows: VirtualProtect and VirtualLock.
- Linux: memfd_secret when the kernel supports it. Otherwise mlock and madvise (
MADV_DONTDUMP) on the malloc path. Amemfd_secretallocation is notmlocked and is not markedMADV_DONTDUMPbymemsec. Every path still usesmprotect(PROT_NONE)between unlocks. - Other Unix (macOS, FreeBSD, …): mlock plus
mprotect(PROT_NONE). FreeBSD/DragonFly also usemadvise(MADV_NOCORE). Nomemfd_secret.
mlock is best-effort: memsec ignores its return value, so hitting RLIMIT_MEMLOCK still constructs. mprotect failure is Error::LockFailed. A failed re-lock after an unlock* scope panics in every profile.
§Usage
§SecureString
use secure_types::SecureString;
// Create a SecureString
let mut secret = SecureString::from("my_super_secret");
// The memory is locked here
// Safely append more data.
secret.push_str("_password");
// The memory is locked here.
// Use a scope to safely access the content as a &str.
secret.unlock_str(|exposed_str| {
assert_eq!(exposed_str, "my_super_secret_password");
});
// When `secret` is dropped, its data zeroized.§SecureVec
use secure_types::SecureVec;
// Create a new, empty secure vector.
let mut secret_key: SecureVec<u8> = SecureVec::new().unwrap();
// Push some sensitive data into it.
secret_key.push(0);
secret_key.push(1);
secret_key.push(2);
// The memory is locked here.
// Use a scope to safely access the contents as a slice.
secret_key.unlock_slice(|unlocked_slice| {
assert_eq!(unlocked_slice, &[0, 1, 2]);
});§SecureArray
use secure_types::SecureArray;
let exposed_array: &mut [u8; 3] = &mut [1, 2, 3];
let mut secure_array = SecureArray::from_slice_mut(exposed_array).unwrap();
secure_array.unlock_mut(|unlocked_slice| {
assert_eq!(unlocked_slice, &[1, 2, 3]);
});§Binary codec
The codec feature is a small binary serde format. Encode goes into locked memory; decode reads out of it. #[derive(Serialize, Deserialize)] and #[serde(...)] work as usual. No extra dependency beyond serde.
use secure_types::{decode, encode, encode_into_vec};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct VaultData {
label: String,
#[serde(default)]
wallet_state_key: Option<u32>,
#[serde(default, skip_serializing)]
contacts: Vec<String>,
}
let vault = VaultData {
label: "main".to_owned(),
wallet_state_key: Some(7),
contacts: vec!["not persisted".to_owned()],
};
// The encoded document is the only copy, and it lives in locked memory that is
// zeroized on drop.
let encoded = encode(&vault)?;
let decoded = decode::<VaultData>(&encoded)?;
assert_eq!(decoded.wallet_state_key, Some(7));
assert!(decoded.contacts.is_empty()); // `skip_serializing` -> `default`
// When the caller is going to hold the document in a `Vec<u8>` anyway,
// `encode_into_vec` appends it straight into that buffer — a codec tag first,
// the document after it — so there is no `SecureBytes` to allocate and nothing
// copied twice. `encode_to_vec` is the same into a fresh buffer.
//
// That buffer is ordinary memory: not locked, and not wiped on drop.
let mut payload = vec![0x07];
encode_into_vec(&mut payload, &vault)?;
assert_eq!(payload[0], 0x07);encode returns a SecureBytes. encode_to_vec / encode_into_vec produce the same document into a plain Vec<u8> the caller owns: not locked, and not zeroized on drop, but a failed encoding erases everything it appended rather than leaving a partial document behind. encoded_len reports how many bytes a document takes — measured by the same serializer, so a destination can be sized exactly and never grow (a growing Vec leaves a copy of the partial document in the allocation it abandons). decode unlocks only for the parse and re-locks afterwards, including on error. Types are raw binary — a SecureArray<u8, 32> is 32 bytes — and strings are not escaped, so there is no scratch copy of an unescaped string.
Format evolution. FORMAT_VERSION is the first byte. An unknown version is refused. Adding a field with #[serde(default)] does not need a bump: fields are named and length-prefixed, so unknown fields are skipped and missing ones take their default. Changing a field’s type does need a bump.
Not supported. No type tags, so deserialize_any is unimplemented. #[serde(flatten)], #[serde(untagged)], and Value-shaped fields fail with DecodeError::Unsupported. Untagged enums can still serialize; they cannot be read back.
§See also the examples.
§Feature Flags
use_os(default): Enables all OS-level security features. Supported on Linux, Windows, and other Unix (macOS, FreeBSD, …); thememfd_secretbacking (and core-dump exclusion viaMADV_DONTDUMP) is Linux-only.no_os: No-op, kept for backwards compatibility.no_stdis selected by disabling the default features (--no-default-features), which leaves only the zeroize-on-drop guarantee.serde: Enables serialization/deserialization.codec: Addsencode/encode_with_capacity/encode_to_vec/encode_to_vec_with_capacity/encode_into_vec/encoded_len/decode/decode_slice, a binary format written into locked memory (or, for the_vecpair, into aVec<u8>you own) and read out of it. Impliesserde, works inno_std+alloc, and adds no dependency.expose-ptr: For testing purposes. Exposes the locked memory region pointer.
§Security notes
- Serialization writes plaintext.
serde_json::to_string/to_vecleave the document in an ordinaryString/Vecthat nothing wipes. Zeroize that buffer yourself, write throughSecureBytesWriter, or use the binary codec. - Deserialization reads a buffer you own.
serde_json::from_str/from_slicetake a plain&str/&[u8]. Parse from inside locked memory (locked.unlock_slice(|json| serde_json::from_slice::<Vault>(json))) so the input is unlocked only for the parse. Escaped JSON strings still land inserde_json’s own scratch buffer, which this crate cannot wipe. The codec decoder has no such scratch.
§Running tests
Public-API tests live in tests/ (one integration crate per module). Internals, memory-protection checks, and crash tests that spawn a child stay in src/ — a fault on locked memory kills the process, so they run in a child. Shared fixtures are in tests/common/.
cargo test # default features
cargo test --all-features
cargo test --features serde,expose-ptr
cargo test --no-default-features --features codec # no_std + alloc, codec only§License
Licensed under the MIT license.
§Credits
Re-exports§
pub use array::SecureArray;pub use string::SecureString;pub use vec::SecureBytes;pub use vec::SecureVec;pub use writer::SecureBytesWriter;pub use memsec;
Modules§
- array
- string
- vec
- writer
- An
std::io::Writeadapter that appends into locked, zeroizing memory.
Enums§
Traits§
- Zeroize
- Trait for securely erasing values from memory.
Functions§
- supports_
memfd_ secret - Reports whether the kernel supports
memfd_secret-backed allocations.