Expand description
§secure-gate
no_std-compatible wrappers for sensitive data with explicit exposure requirements.
Fixed<T>- Stack-allocated wrapperDynamic<T>- Heap-allocated wrapperFixedRng<N>- Cryptographically secure random bytes of fixed length NDynamicRng- Heap-allocated cryptographically secure random bytesCloneableArray<const N: usize>- Cloneable fixed-size stack secret ([u8; N])CloneableString- Cloneable heap-allocated text secret (String)CloneableVec- Cloneable heap-allocated binary secret (Vec<u8>)HexString- Validated lowercase hexadecimal string wrapperBase64String- Validated URL-safe base64 string wrapper (no padding)Bech32String- Validated Bech32/Bech32m string wrapper With thezeroizefeature enabled, memory containing secrets is zeroed on drop, including spare capacity where applicable. Access to secret data requires an explicit.expose_secret()call. There are noDerefimplementations or other implicit access paths. Cloning is opt-in and only available under thezeroizefeature.
§Installation
[dependencies]
secure-gate = "0.7.0-rc.5"Recommended configuration:
secure-gate = { version = "0.7.0-rc.5", features = ["full"] }§Features
| Feature | Description |
|---|---|
zeroize | Memory zeroing on drop and opt-in cloning via pre-baked cloneable types |
rand | Random generation (FixedRng<N>::generate(), DynamicRng::generate()) |
ct-eq | Constant-time equality comparison |
encoding | All encoding support (encoding-hex, encoding-base64, encoding-bech32) |
encoding-hex | Hex encoding, HexString, FixedRng hex methods |
encoding-base64 | Base64String |
encoding-bech32 | Bech32String (Bech32 and Bech32m variants; supports mixed-case input, stores lowercase) |
full | All optional features |
The crate is no_std-compatible with alloc. Features are optional and add no overhead when unused.
§Security Model & Design Philosophy
secure-gate prioritizes auditability and explicitness over implicit convenience.
Every access to secret material - even inside the crate itself - goes through a method named .expose_secret() (or .expose_secret_mut()). This is deliberate:
- Makes every exposure site grep-able and obvious in code reviews
- Prevents accidental silent leaks or hidden bypasses
- Ensures consistent reasoning about secret lifetimes and memory handling
These calls are
#[inline(always)] const fnreborrows - the optimizer elides them completely. There is zero runtime cost. It’s intentional “theatre” for humans and auditors, but free for the machine. Clarity of purpose wins over micro-optimizations.
§Quick Start
use secure_gate::{fixed_alias, dynamic_alias};
fixed_alias!(pub Aes256Key, 32);
dynamic_alias!(pub Password, String);
let pw: Password = "hunter2".into();
assert_eq!(pw.expose_secret(), "hunter2");
#[cfg(feature = "zeroize")]
{
use secure_gate::{CloneableArray, CloneableString, CloneableVec};
let key: CloneableArray<32> = [0u8; 32].into();
let pw: CloneableString = "hunter2".into();
let seed: CloneableVec = vec![0u8; 64].into();
let key2 = key.clone();
let pw2 = pw.clone();
let seed2 = seed.clone();
}
#[cfg(feature = "rand")]
{
use secure_gate::fixed_alias_rng;
fixed_alias_rng!(pub MasterKey, 32);
#[cfg(feature = "encoding-hex")]
{
let key = MasterKey::generate();
let hex = key.into_hex();
println!("key hex: {}", hex.expose_secret());
}
#[cfg(feature = "encoding-base64")]
{
let key = MasterKey::generate();
let base64 = key.into_base64();
println!("key base64: {}", base64.expose_secret());
}
#[cfg(feature = "encoding-bech32")]
{
let key1 = MasterKey::generate();
let bech32 = key1.into_bech32("example");
println!("key bech32: {}", bech32.expose_secret());
let key2 = MasterKey::generate();
let bech32m = key2.into_bech32m("example");
println!("key bech32m: {}", bech32m.expose_secret());
}
}§Opt-In Cloning
Cloning is available only when the zeroize feature is enabled.
The crate provides three ready-to-use cloneable primitives (zero boilerplate):
| Type | Allocation | Inner Data | Typical Use Case |
|---|---|---|---|
CloneableArray<const N: usize> | Stack | [u8; N] | Fixed-size keys/nonces |
CloneableString | Heap | String | Passwords, tokens, API keys |
CloneableVec | Heap | Vec<u8> | Seeds, variable-length binary |
#[cfg(feature = "zeroize")]
{
use secure_gate::{CloneableArray, CloneableString, CloneableVec};
let key: CloneableArray<32> = [0u8; 32].into();
let mut pw: CloneableString = "hunter2".into();
let seed: CloneableVec = vec![0u8; 64].into();
let key2 = key.clone(); // Safe deep clone
let pw2 = pw.clone();
let seed2 = seed.clone();
// Convenience access to inner values
pw.expose_inner_mut().push('!');
assert_eq!(pw.expose_inner(), "hunter2!");
}§Semantic Aliases (Recommended)
For better readability, create type aliases:
#[cfg(feature = "zeroize")]
{
use secure_gate::{CloneableArray, CloneableString, CloneableVec};
pub type CloneablePassword = CloneableString;
pub type CloneableAes256Key = CloneableArray<32>;
pub type CloneableSeed = CloneableVec;
}These are zero-cost and make intent crystal clear.
§Minimizing Stack Exposure
When reading secrets from user input (e.g., passwords), use init_with/try_init_with to reduce temporary stack exposure:
#[cfg(feature = "zeroize")]
{
use secure_gate::{CloneableString, CloneableVec, CloneableArray};
let pw = CloneableString::init_with(|| {
// Read from terminal, network, etc.
"hunter2".to_string()
});
let seed = CloneableVec::init_with(|| {
// Read binary data...
vec![0u8; 64]
});
let key = CloneableArray::<32>::init_with(|| {
// Read fixed-size data...
[0u8; 32]
});
// Or fallible:
let pw = CloneableString::try_init_with(|| {
Ok::<String, &str>("hunter2".to_string())
}).unwrap();
}The temporary is cloned to the heap and zeroized immediately.
§Randomness
#[cfg(feature = "rand")]
{
use secure_gate::fixed_alias_rng;
fixed_alias_rng!(pub JwtSigningKey, 32);
fixed_alias_rng!(pub BackupCode, 16);
let key = JwtSigningKey::generate();
#[cfg(feature = "encoding-hex")]
{
let code = BackupCode::generate();
let hex_code = code.into_hex();
println!("Backup code: {}", hex_code.expose_secret());
}
#[cfg(feature = "encoding-base64")]
{
let code = BackupCode::generate();
let base64_code = code.into_base64();
println!("Backup code: {}", base64_code.expose_secret());
}
}FixedRng<N> can only be constructed via cryptographically secure RNG.
Direct generation is also available:
#[cfg(feature = "rand")]
{
use secure_gate::{Fixed, Dynamic};
let key: Fixed<[u8; 32]> = Fixed::generate_random();
let random: Dynamic<Vec<u8>> = Dynamic::generate_random(64);
}§Encoding
#[cfg(feature = "encoding-hex")]
{
use secure_gate::{encoding::hex::HexString, SecureEncodingExt};
let bytes = [0u8; 16];
let hex: String = bytes.to_hex();
let hex_upper: String = bytes.to_hex_upper();
let validated = HexString::new("deadbeef".to_string()).unwrap();
let decoded = validated.into_bytes();
}
#[cfg(feature = "encoding-base64")]
{
use secure_gate::encoding::base64::Base64String;
let validated = Base64String::new("SGVsbG8".to_string()).unwrap();
let decoded = validated.into_bytes();
}
#[cfg(feature = "encoding-bech32")]
{
use secure_gate::encoding::bech32::Bech32String;
// Bech32 (e.g., Bitcoin segwit)
let bech32 = Bech32String::new("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4".to_string()).unwrap();
assert!(bech32.is_bech32());
// Bech32m (e.g., age keys)
let bech32m = Bech32String::new("abc14w46h2at4w46h2at4w46h2at4w46h2at958ngu".to_string()).unwrap();
assert!(bech32m.is_bech32m());
}Encoding functions require explicit .expose_secret(). Invalid inputs to the .new() constructors are zeroed when the zeroize feature is enabled.
§Constant-Time Equality
#[cfg(feature = "ct-eq")]
{
use secure_gate::Fixed;
let a = Fixed::<[u8; 32]>::generate_random();
let b = Fixed::<[u8; 32]>::generate_random();
assert!(a.ct_eq(&a));
}Available on Fixed<[u8; N]> and Dynamic<T> where T: AsRef<[u8]>.
§Macros
use secure_gate::{fixed_alias, dynamic_alias};
fixed_alias!(pub Aes256Key, 32);
dynamic_alias!(pub Password, String);
#[cfg(feature = "rand")]
{
use secure_gate::fixed_alias_rng;
fixed_alias_rng!(pub MasterKey, 32);
}§Memory Guarantees (zeroize enabled)
| Type | Allocation | Auto-zero | Full wipe | Slack eliminated | Notes |
|---|---|---|---|---|---|
Fixed<T> | Stack | Yes | Yes | Yes (no heap) | |
Dynamic<T> | Heap | Yes | Yes | No (until drop) | Use shrink_to_fit() |
FixedRng<N> | Stack | Yes | Yes | Yes | |
HexString | Heap | Yes (invalid input) | Yes | No (until drop) | Validated hex |
Base64String | Heap | Yes (invalid input) | Yes | No (until drop) | Validated base64 |
Bech32String | Heap | Yes (invalid input) | Yes | No (until drop) | Validated Bech32/Bech32m |
§Performance
The wrappers add no runtime overhead compared to raw types in benchmarks.
§Changelog
§License
MIT OR Apache-2.0
Re-exports§
pub use cloneable::CloneableSecretMarker;pub use cloneable::CloneableArray;pub use cloneable::CloneableString;pub use cloneable::CloneableVec;pub use random::DynamicRng;pub use random::FixedRng;
Modules§
- cloneable
- Cloneable secret primitives for handling sensitive data that can be safely duplicated.
- ct_eq
- Constant-time equality comparison - requires “ct-eq” feature. Prevents timing attacks when comparing sensitive data. Provides the ConstantTimeEq trait for secure comparisons. Constant-time equality comparison for cryptographic secrets (gated behind “ct-eq”).
- encoding
- Encoding utilities for secrets - various encoding features available. Secure encoding/decoding with validation and zeroization. Encoding utilities for secure handling of encoded secret data.
- random
- Cryptographically secure random generation - requires “rand” feature. Provides RNG-backed secret generation with freshness guarantees. Cryptographically secure random value generation with encoding conveniences (gated behind “rand” and encoding features).
Macros§
- dynamic_
alias - Creates a type alias for a heap-allocated secure secret.
- dynamic_
generic_ alias - Creates a generic heap-allocated secure secret type alias.
- fixed_
alias - Creates a type alias for a fixed-size secure secret.
- fixed_
alias_ rng - Creates a type alias for a random-only fixed-size secret.
- fixed_
generic_ alias - Creates a generic (const-sized) fixed secure buffer type.
Structs§
- Dynamic
- Heap-allocated secure secret wrapper.
- Fixed
- Stack-allocated secure secret wrapper.
- From
Slice Error - Error for slice length mismatches in TryFrom impls.