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
[]
= "0.6.1"
Recommended (maximum safety + ergonomics):
= { = "0.6.1", = ["full"] }
Or explicitly:
= { = "0.6.1", = ["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 ;
fixed_alias!; // Explicit visibility required
dynamic_alias!; // Explicit visibility required
// Heap secrets – unchanged ergonomics
let pw: Password = "hunter2".into;
assert_eq!;
Type-Safe Randomness
- 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:
Direct Random Generation
For convenience, you can generate random secrets directly without going through FixedRng:
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
Creating Secrets from Encoded Strings
You can create Fixed<[u8; N]> secrets directly from hex or base64url strings:
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 ;
fixed_alias!; // Public type
fixed_alias!; // Private type (no visibility modifier)
fixed_alias!; // Crate-visible type
dynamic_alias!; // Public type
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:
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.