zero-secrets
Simple lightweight secret wrappers that zeroize on drop and prevent accidental logging.
Built on RustCrypto zeroize
Like secrecy but with fewer heap allocations.
Usage
The crate provides three owned wrappers, each zeroizing its backing storage on
drop and redacting Debug:
SecretString— owns a heapString(seeds,.credstext, invite codes).SecretBytes— owns a heapVec<u8>(decrypted buffers, credential blobs).SecretArray<N>— owns a fixed-size[u8; N](X25519 seeds, AEAD keys).
Wrapping and reading a secret
Wrap an owned value, then borrow it through expose_secret for use. The borrow
never takes ownership, and the secret is still zeroized when the wrapper drops.
use SecretString;
let secret = new;
// Borrow as &str without taking ownership.
assert_eq!;
assert_eq!;
assert!;
// The backing String is zeroized here, when `secret` is dropped.
From is implemented for ergonomic construction:
use SecretString;
let secret: SecretString = "abc".to_string.into;
assert_eq!;
Bytes and fixed-size keys
use ;
// Heap-backed bytes (e.g. a decrypted plaintext buffer).
let blob = new;
assert_eq!;
// Stack-backed fixed-size key material (e.g. a 32-byte AEAD key).
let key = new;
assert_eq!;
assert_eq!;
Debug is redacted
Debug never prints the contents — or even the length — so a secret can't leak
through a stray {:?}. Display is intentionally not implemented, so {} does
not compile.
use SecretString;
let secret = new;
assert_eq!;
Clone is an independent, independently-zeroized copy
A clone owns its own allocation; dropping one does not affect the other, and both zeroize on their own drop. This holds for clones moved into async blocks/tasks.
use SecretBytes;
let original = new;
let cloned = original.clone;
assert_eq!;
drop; // independent allocation; `original` is untouched
assert_eq!;
Handing ownership out of the library
When a secret must cross the library boundary, the #[must_use] extractors
return the plain owned value. The moved-out value is intentionally not
zeroized — that is the point of the handoff, and the caller then owns leak
prevention. The wrapper is consumed, so no wrapped copy lingers.
use ;
let s = new.into_string; // -> String
let v = new.into_vec; // -> Vec<u8>
let k = new.into_inner; // -> [u8; 4]
Recommended policy
These wrappers are designed to be internal to your crate around private fields.
Keep Secret* types out of your public API: accept &str / &[u8] in your inputs,
and return borrowed accessors via expose_secret for normal use. Use plain owned
extractors only when ownership must leave the boundary.
serde::Serialize / Deserialize are intentionally not implemented.
If serializing is needed, it should be in targeted, auditable call-sites.