Skip to main content

nist_rand/
lib.rs

1//! # nist-rand — SP 800-90A/B/C CSPRNG Implementation
2//!
3//! > **⚠️ DISCLAIMER:** This code is **NOT FIPS Verified** and has **NOT** been 
4//! > officially audited by a NIST lab. Furthermore, this code does not receive 
5//! > any formal security audits. It implements the best practices and algorithms 
6//! > described by NIST SP 800-90A/B/C, but does not claim or provide official 
7//! > compliance or certification. Use at your own risk.
8//!
9//! A high-assurance, production-ready Cryptographically Secure Pseudorandom Number
10//! Generator (CSPRNG) that implements the architectural design and algorithms
11//! described in **NIST SP 800-90A/B/C**.
12//!
13//! ## Architecture
14//!
15//! ```text
16//!  ┌───────────────────────────────────────────────────────────────┐
17//!  │                    SP 800-90C Blender                         │
18//!  │  ┌──────────────┐  ┌────────────────┐  ┌──────────────────┐   │
19//!  │  │ OS RNG       │  │ CPU Jitter     │  │ System State     │   │
20//!  │  │ /dev/urandom │  │ (advance feat) │  │ /proc, TSC, ASLR │   │
21//!  │  │ + 90B tests  │  │ Von Neumann    │  │ (advance feat)   │   │
22//!  │  └──────┬───────┘  └───────┬────────┘  └────────┬─────────┘   │
23//!  │         └──────────────────┴────────────────────┘             │
24//!  │                            │ SHA3-512                         │
25//!  └────────────────────────────┼──────────────────────────────────┘
26//!                               │ 512-bit conditioned seed
27//!  ┌────────────────────────────▼──────────────────────────────────┐
28//!  │             Hash_DRBG (SP 800-90A § 10.1.1)                   │
29//!  │                    SHA3-512 (FIPS 202)                        │
30//!  │                reseed every 10 000 requests                   │
31//!  └────────────────────────────┬──────────────────────────────────┘
32//!                               │
33//!                         fill() / NistRng
34//! ```
35//!
36//! ## SP 800-90 Design Principles
37//!
38//! | Requirement | Standard | Implementation |
39//! |-------------|----------|----------------|
40//! | Entropy source health tests | SP 800-90B § 4.4 | `rng::HealthTests` (RCT + APT) |
41//! | Multi-source entropy blending | SP 800-90C § 4.1 | `entropy::get_blended_entropy()` with SHA3-512 |
42//! | Approved DRBG mechanism | SP 800-90A § 10.1.1 | `drbg::HashDrbg` |
43//! | Approved hash function | FIPS 202 | SHA3-512 |
44//! | Reseed interval enforcement | SP 800-90A Table 2 | Panic-on-overrun after 10 000 calls |
45//! | Fail-safe on entropy failure | FIPS 140-3 § 9.2 (Aligns with) | `panic!` on health-test or reseed failure |
46//!
47//! ## Crate Features
48//!
49//! | Feature | Default | Description |
50//! |---------|---------|-------------|
51//! | `rand_core` | ✓ | Exposes [`NistRng`] implementing `rand_core` 0.10 traits |
52//! | `advance` | ✓ | Adds CPU jitter, System state, and Hardware RNG (RDSEED/RDRAND) + extended 90B health tests |
53//! | `build_separator` | ✓ | Embeds a random build-time salt (via `build.rs`) |
54//! | `zeroize` | ✓ | Derives [`zeroize::ZeroizeOnDrop`] on `HashDrbg`; wraps all sensitive buffers in [`zeroize::Zeroizing`] so keying material is erased even on panic |
55//! | `exp_simd_rng` | ✗ | Experimental: AVX-256 thermal jitter (implies `advance`) |
56//! | `exp_network_rng` | ✗ | Experimental: UDP network-latency jitter (implies `advance`) |
57
58#![cfg_attr(docsrs, feature(doc_cfg))]
59#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]
60#![deny(clippy::undocumented_unsafe_blocks)]
61
62/// Hash_DRBG (SP 800-90A § 10.1.1) backed by SHA3-512.
63pub mod drbg;
64/// Entropy sources and SP 800-90B continuous health tests.
65pub mod entropy;
66/// OS RNG reader and `HealthTests` state machine.
67pub mod rng;
68
69use drbg::HashDrbg;
70use std::cell::RefCell;
71
72// ────────────────────────────────────────────────────────────────────────────────
73// Thread-local DRBG instance
74// ────────────────────────────────────────────────────────────────────────────────
75
76thread_local! {
77    /// Per-thread `Hash_DRBG` instance, seeded lazily on first access.
78    ///
79    /// Using a thread-local avoids all locking overhead and data-race hazards.
80    /// Each thread independently reseeds from the blended entropy pipeline;
81    /// threads therefore contribute independent randomness to their outputs.
82    static DRBG: RefCell<HashDrbg> = RefCell::new({
83        let seed = entropy::get_blended_entropy();
84        HashDrbg::instantiate(&seed)
85    });
86}
87
88// ────────────────────────────────────────────────────────────────────────────────
89// Public API
90// ────────────────────────────────────────────────────────────────────────────────
91
92/// Fills `dest` with cryptographically secure random bytes.
93///
94/// Bytes are produced by the thread-local SP 800-90A `Hash_DRBG` (SHA3-512).
95/// When the DRBG's reseed counter is exhausted, fresh SP 800-90C-conditioned
96/// entropy is gathered automatically before retrying.
97///
98/// # FIPS 140-3 Fail-Safe
99///
100/// This function **panics** if:
101/// - The OS entropy source fails its SP 800-90B health tests, **or**
102/// - Generation still fails after an automatic reseed (should never happen in a
103///   correctly functioning environment).
104///
105/// Panicking is the correct FIPS 140-3 response to an entropy-source failure:
106/// the module must enter an error state rather than produce predictable output.
107///
108/// # Example
109///
110/// ```rust
111/// let mut key = [0u8; 32];
112/// nist_rand::fill(&mut key);
113/// assert!(key.iter().any(|&b| b != 0));
114/// ```
115pub fn fill(dest: &mut [u8]) {
116    DRBG.with(|cell| {
117        let mut drbg = cell.borrow_mut();
118
119        if drbg.generate(dest) {
120            return;
121        }
122
123        // Reseed counter exhausted — gather fresh entropy and retry.
124        let seed = entropy::get_blended_entropy();
125        drbg.reseed(&seed);
126
127        dest.fill(0); // Prevent leaking any pre-existing caller stack data
128
129        if !drbg.generate(dest) {
130            panic!(
131                "nist-rand: DRBG failed to generate output after automatic reseed. \
132                 This indicates a critical entropy source failure."
133            );
134        }
135    });
136}
137
138/// Forces an immediate reseed of the thread-local DRBG using fresh,
139/// SP 800-90C-conditioned entropy.
140///
141/// This provides Forward Secrecy on demand: an attacker who compromises
142/// the system state *after* this call cannot determine any outputs
143/// generated *before* this call.
144#[must_use = "this function only has side effects on the internal state"]
145pub fn reseed() {
146    DRBG.with(|cell| {
147        let mut drbg = cell.borrow_mut();
148        let seed = entropy::get_blended_entropy();
149        drbg.reseed(&seed);
150    });
151}
152
153/// Fills `dest` with cryptographically secure random bytes, with guaranteed
154/// **Prediction Resistance** (SP 800-90A § 8.7.2).
155///
156/// This function gathers fresh physical entropy from the underlying OS/Jitter
157/// sources and forces an immediate reseed of the thread-local DRBG **before**
158/// generating the requested bytes.
159///
160/// Use this for ultra-sensitive material (e.g., long-term root keys or CA
161/// private keys) where you want absolute assurance that the output is tied
162/// to fresh physical entropy gathered at the exact moment of the request.
163///
164/// # Performance Warning
165/// This is significantly slower than [`fill`] because it invokes the underlying
166/// OS RNG and Jitter sources on every call. For most cryptographic purposes,
167/// [`fill`] is completely secure and vastly faster.
168pub fn fill_prediction_resistant(dest: &mut [u8]) {
169    DRBG.with(|cell| {
170        let mut drbg = cell.borrow_mut();
171
172        // Force fresh entropy gathering and reseed BEFORE generation
173        let seed = entropy::get_blended_entropy();
174        drbg.reseed(&seed);
175
176        if !drbg.generate(dest) {
177            panic!(
178                "nist-rand: DRBG failed to generate output with Prediction Resistance. \
179                 This indicates a critical entropy source failure."
180            );
181        }
182    });
183}
184
185// ────────────────────────────────────────────────────────────────────────────────
186// Optional rand_core 0.10 integration
187// ────────────────────────────────────────────────────────────────────────────────
188
189#[cfg(feature = "rand_core")]
190#[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))]
191pub use rand_core_impl::{NistRng, NistRngPredictionResistant};
192
193#[cfg(feature = "rand_core")]
194mod rand_core_impl {
195    use super::fill;
196    use core::convert::Infallible;
197    use rand_core::{TryCryptoRng, TryRng};
198
199    /// A `rand_core` 0.10-compatible wrapper around the nist-rand pipeline.
200    ///
201    /// Implements [`TryRng`]`<Error = `[`Infallible`]`>`, which automatically
202    /// grants the blanket [`rand_core::Rng`] implementation, and
203    /// [`TryCryptoRng`], which automatically grants the blanket
204    /// [`rand_core::CryptoRng`] implementation.
205    ///
206    /// # Usage
207    ///
208    /// ```rust,ignore
209    /// use nist_rand::NistRng;
210    /// use rand_core::RngExt; // or rand::RngExt
211    ///
212    /// let mut rng = NistRng;
213    /// let key: [u8; 32] = rng.random();
214    /// ```
215    ///
216    /// # Thread Safety
217    ///
218    /// `NistRng` is a zero-sized type.  It is **not** `Sync` — each thread
219    /// should hold its own instance.  The underlying DRBG state is thread-local.
220    #[derive(Debug, Default)]
221    pub struct NistRng;
222
223    impl TryRng for NistRng {
224        type Error = Infallible;
225
226        #[inline]
227        fn try_next_u32(&mut self) -> Result<u32, Infallible> {
228            let mut buf = [0u8; 4];
229            fill(&mut buf);
230            Ok(u32::from_le_bytes(buf))
231        }
232
233        #[inline]
234        fn try_next_u64(&mut self) -> Result<u64, Infallible> {
235            let mut buf = [0u8; 8];
236            fill(&mut buf);
237            Ok(u64::from_le_bytes(buf))
238        }
239
240        #[inline]
241        fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
242            fill(dest);
243            Ok(())
244        }
245    }
246
247    /// Marks `NistRng` as a cryptographically secure source.
248    ///
249    /// The blanket `impl<R: TryCryptoRng<Error = Infallible>> CryptoRng for R`
250    /// in `rand_core` then grants the full `CryptoRng` bound automatically.
251    impl TryCryptoRng for NistRng {}
252
253    /// A `rand_core` 0.10-compatible wrapper providing **Prediction Resistance**.
254    ///
255    /// Like [`NistRng`], but backed by [`fill_prediction_resistant`]. Every
256    /// generation request forces a fresh gather of physical entropy before
257    /// producing output.
258    #[derive(Debug, Default)]
259    pub struct NistRngPredictionResistant;
260
261    impl TryRng for NistRngPredictionResistant {
262        type Error = Infallible;
263
264        #[inline]
265        fn try_next_u32(&mut self) -> Result<u32, Infallible> {
266            let mut buf = [0u8; 4];
267            super::fill_prediction_resistant(&mut buf);
268            Ok(u32::from_le_bytes(buf))
269        }
270
271        #[inline]
272        fn try_next_u64(&mut self) -> Result<u64, Infallible> {
273            let mut buf = [0u8; 8];
274            super::fill_prediction_resistant(&mut buf);
275            Ok(u64::from_le_bytes(buf))
276        }
277
278        #[inline]
279        fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
280            super::fill_prediction_resistant(dest);
281            Ok(())
282        }
283    }
284
285    impl TryCryptoRng for NistRngPredictionResistant {}
286}
287
288// ────────────────────────────────────────────────────────────────────────────────
289// Tests
290// ────────────────────────────────────────────────────────────────────────────────
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295    use std::collections::HashSet;
296
297    #[test]
298    fn fill_basic_sanity() {
299        let mut buf = [0u8; 128];
300        fill(&mut buf);
301        assert!(buf.iter().any(|&b| b != 0), "output should not be all-zero");
302    }
303
304    #[test]
305    fn fill_uniqueness() {
306        let mut seen = HashSet::new();
307        for _ in 0..10 {
308            let mut buf = [0u8; 32];
309            fill(&mut buf);
310            assert!(seen.insert(buf.to_vec()), "CRITICAL: duplicate DRBG output detected");
311        }
312    }
313
314    #[test]
315    fn fill_multi_block() {
316        // 4 KiB is much larger than a single SHA3-512 block (64 bytes).
317        let mut buf = [0u8; 4096];
318        fill(&mut buf);
319        assert!(buf.iter().any(|&b| b != 0));
320        assert_ne!(&buf[..64], &buf[64..128], "consecutive blocks must differ");
321    }
322}