Expand description
§secure-gate
Zero-cost, no_std-compatible wrappers for sensitive data with enforced explicit exposure.
Fixed<T>– Stack-allocated, zero-cost wrapperDynamic<T>– Heap-allocated wrapper with full.into()ergonomicsFixedRng<N>– Cryptographically secure random bytes of exact length NRandomHex– Validated random hex string that can only be constructed from fresh RNG
When the zeroize feature is enabled, secrets are automatically wiped on drop (including spare capacity).
All access to secret bytes requires an explicit .expose_secret() call – no silent leaks, no Deref, no hidden methods, no into_inner() bypasses.
§Installation
[dependencies]
secure-gate = "0.6.1"Recommended (maximum safety + ergonomics):
secure-gate = { version = "0.6.1", features = ["full"] }Or explicitly:
secure-gate = { version = "0.6.1", features = ["zeroize", "rand", "conversions"] }§Features
| Feature | Description |
|---|---|
zeroize | Automatic memory wiping on drop – strongly recommended (enabled by default) |
rand | FixedRng<N>::generate() + fixed_alias_rng! – type-safe, fresh randomness |
conversions | .to_hex(), .to_hex_upper(), .to_base64url(), .ct_eq() + HexString / RandomHex |
full | Convenience feature that enables all optional features (zeroize, rand, conversions) |
Works in no_std + alloc. Only pay for what you use.
§Quick Start
use secure_gate::{fixed_alias, dynamic_alias};
fixed_alias!(pub Aes256Key, 32); // Explicit visibility required
dynamic_alias!(pub Password, String); // Explicit visibility required
#[cfg(feature = "rand")]
{
use secure_gate::fixed_alias_rng;
fixed_alias_rng!(pub MasterKey, 32); // Explicit visibility required
fixed_alias_rng!(pub Nonce, 24); // Explicit visibility required
let key = MasterKey::generate(); // FixedRng<32>
let nonce = Nonce::generate(); // FixedRng<24>
#[cfg(feature = "conversions")]
{
use secure_gate::RandomHex;
let hex_token: RandomHex = MasterKey::random_hex(); // Only from fresh RNG
}
}
// Heap secrets – unchanged ergonomics
let pw: Password = "hunter2".into();
assert_eq!(pw.expose_secret(), "hunter2");§Type-Safe Randomness
#[cfg(feature = "rand")]
{
use secure_gate::fixed_alias_rng;
fixed_alias_rng!(pub JwtSigningKey, 32); // Explicit visibility required
fixed_alias_rng!(pub BackupCode, 16); // Explicit visibility required
let key = JwtSigningKey::generate(); // FixedRng<32>
let code = BackupCode::generate(); // FixedRng<16>
#[cfg(feature = "conversions")]
{
use secure_gate::RandomHex;
let hex_code: RandomHex = BackupCode::random_hex();
println!("Backup code: {}", hex_code.expose_secret());
}
}- Guaranteed freshness –
FixedRng<N>can only be constructed via secure RNG - Zero-cost – Newtype over
Fixed, fully inlined - Explicit visibility – All macros require clear visibility specification (
pub,pub(crate), or private) .generate()is the canonical constructor (.new()is deliberately unavailable)
§Converting RNG Types
When you need to convert FixedRng or DynamicRng to their base types:
#[cfg(feature = "rand")]
{
use secure_gate::{Fixed, Dynamic, rng::{FixedRng, DynamicRng}};
// Convert FixedRng to Fixed (preserves security guarantees)
let key: Fixed<[u8; 32]> = FixedRng::<32>::generate().into();
// Or explicitly:
let key: Fixed<[u8; 32]> = FixedRng::<32>::generate().into_inner();
// Convert DynamicRng to Dynamic
let random: Dynamic<Vec<u8>> = DynamicRng::generate(64).into();
}§Direct Random Generation
For convenience, you can generate random secrets directly without going through FixedRng:
#[cfg(feature = "rand")]
{
use secure_gate::{Fixed, Dynamic};
// Direct generation (most ergonomic)
let key: Fixed<[u8; 32]> = Fixed::generate_random();
let random: Dynamic<Vec<u8>> = Dynamic::generate_random(64);
// Equivalent to:
// FixedRng::<32>::generate().into() // or .into_inner() which returns Fixed
// DynamicRng::generate(64).into() // or .into_inner() which returns Dynamic
}Note: FixedRng/DynamicRng preserve the type-level guarantee that values came from RNG. Converting to Fixed/Dynamic loses that guarantee but enables mutation if needed.
§Secure Conversions – conversions feature
#[cfg(all(feature = "rand", feature = "conversions"))]
{
use secure_gate::fixed_alias_rng;
use secure_gate::SecureConversionsExt;
fixed_alias_rng!(pub Aes256Key, 32); // Explicit visibility required
let key = Aes256Key::generate();
let other = Aes256Key::generate();
let hex = key.expose_secret().to_hex(); // "a1b2c3d4..."
let b64 = key.expose_secret().to_base64url(); // URL-safe, no padding
let same = key.expose_secret().ct_eq(other.expose_secret()); // Constant-time
}§Creating Secrets from Encoded Strings
You can create Fixed<[u8; N]> secrets directly from hex or base64url strings:
#[cfg(feature = "conversions")]
{
use secure_gate::Fixed;
// From hex string
let key = Fixed::<[u8; 4]>::from_hex("deadbeef").unwrap();
assert_eq!(key.expose_secret(), &[0xde, 0xad, 0xbe, 0xef]);
// From base64url string (no padding)
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
let b64 = URL_SAFE_NO_PAD.encode([0xde, 0xad, 0xbe, 0xef]);
let key2 = Fixed::<[u8; 4]>::from_base64url(&b64).unwrap();
assert_eq!(key2.expose_secret(), &[0xde, 0xad, 0xbe, 0xef]);
}Both methods are memory-hardened: temporary buffers are automatically zeroized on error or after successful copy (when zeroize feature is enabled).
Why .expose_secret() is required
Every secret access is loud, grep-able, and auditable. There are no methods on the wrapper types that expose bytes directly. The security model is strictly enforced: Fixed<T>, Dynamic<T>, FixedNoClone<T>, and DynamicNoClone<T> do not provide into_inner() methods that would bypass the explicit exposure requirement. This ensures all secret access is traceable and prevents accidental security violations.
§Macros
use secure_gate::{fixed_alias, dynamic_alias};
fixed_alias!(pub Aes256Key, 32); // Public type
fixed_alias!(private_key, 32); // Private type (no visibility modifier)
fixed_alias!(pub(crate) InternalKey, 64); // Crate-visible type
dynamic_alias!(pub Password, String); // Public type
#[cfg(feature = "rand")]
{
use secure_gate::fixed_alias_rng;
fixed_alias_rng!(pub MasterKey, 32); // FixedRng<32>
}§Memory Guarantees (zeroize enabled)
| Type | Allocation | Auto-zero | Full wipe | Slack eliminated | Notes |
|---|---|---|---|---|---|
Fixed<T> | Stack | Yes | Yes | Yes (no heap) | Zero-cost |
Dynamic<T> | Heap | Yes | Yes | No (until drop) | Use expose_secret_mut().shrink_to_fit() |
FixedRng<N> | Stack | Yes | Yes | Yes | Fresh + type-safe |
RandomHex | Heap | Yes | Yes | No (until drop) | Validated random hex |
§Explicit Zeroization
When the zeroize feature is enabled, you can explicitly zeroize secrets immediately:
#[cfg(feature = "zeroize")]
{
use secure_gate::{Fixed, Dynamic};
let mut key = Fixed::new([42u8; 32]);
// ... use key ...
key.zeroize_now(); // Explicit wipe - makes intent clear
let mut password = Dynamic::<String>::new("secret".to_string());
// ... use password ...
password.zeroize_now(); // Immediate memory wipe
}This is useful when you want to wipe memory before the value goes out of scope, or when you want to make the zeroization intent explicit in the code.
§Performance (Measured December 2025)
Benchmarked on:
Windows 11 Pro, Intel Core i7-10510U @ 1.80 GHz, 16 GB RAM, Rust 1.88.0 (2025-06-23)
cargo bench -p secure-gate --all-features
| Implementation | Time per access (100 samples) | Δ vs raw array |
|---|---|---|
raw [u8; 32] access | 492.22 ps – 501.52 ps | baseline |
Fixed<[u8; 32]> + .expose_secret() | 476.92 ps – 487.12 ps | −3.0 % to −23.9 % |
fixed_alias! (RawKey explicit access | 475.07 ps – 482.91 ps | −3.4 % to −30.5 % |
All implementations are statistically indistinguishable from raw arrays at the picosecond level.
The explicit .expose_secret() path incurs no measurable overhead.
§Changelog
§License
MIT OR Apache-2.0
v0.6.1 adds ergonomic RNG conversions and convenience methods while maintaining strict security guarantees.
All secret access is explicit. No silent leaks remain. Security fix: Removed into_inner() from Fixed/Dynamic types to enforce the security model. Use expose_secret() or expose_secret_mut() for all secret access.
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.
- Dynamic
NoClone - Non-cloneable heap-allocated secret wrapper.
- Fixed
- Stack-allocated secure secret wrapper.
- Fixed
NoClone - Non-cloneable stack-allocated secret wrapper.