purecrypto 0.6.10

A pure-Rust cryptography toolkit with no foreign-code dependencies, from constant-time primitives up to keys, X.509 and TLS.
Documentation
//! HMAC-DRBG — deterministic random bit generator (NIST SP 800-90A), generic
//! over any [`Digest`].

use super::{CryptoRng, RngCore};
use crate::hash::{Digest, Hmac};

/// HMAC-DRBG instantiated with hash function `D`.
///
/// Seed it with [`new`](HmacDrbg::new) from a high-entropy source (e.g.
/// [`OsRng`](super::OsRng)), then draw bytes via [`RngCore`]. Given the same
/// seed it is fully deterministic, which is what makes it testable against the
/// NIST known-answer vectors.
///
/// SP 800-90A §10.1.2.4 mandates reseeding before the counter exceeds `2^48`
/// (the "reseed_interval"). This implementation enforces the limit: once
/// reached, [`generate`](Self::generate) panics until [`reseed`](Self::reseed)
/// is called. Per SP 800-90A the panic-vs-error choice is implementation
/// defined; panic is the conservative behaviour for a CSPRNG.
///
/// # Production use — fork safety and reseeding
///
/// `HmacDrbg` is a *deterministic* generator: given the same seed it
/// produces the same output forever. That is desirable for known-answer
/// tests, for protocols that require deterministic nonces (e.g. RFC 6979
/// ECDSA signatures), and for replay debugging, but it makes the type
/// **unsafe for naive production use**. In particular:
///
/// * **Fork safety.** A `HmacDrbg` cloned across `fork(2)` (whether by an
///   explicit `Clone` or by virtue of being captured in a parent's heap
///   that the child inherits) generates the *same* output stream on
///   both sides of the fork. The two processes will then produce
///   colliding nonces, blinding factors, or session secrets. Either
///   re-seed in the child via [`OsRng`](super::OsRng) before any draw,
///   or use [`OsRng`](super::OsRng) directly in fork-prone code paths.
/// * **Initial entropy.** Seeding from a low-entropy source (e.g. a
///   constant or a millisecond timestamp) leaks the entire stream to
///   anyone who can guess the seed. Production callers MUST seed from
///   the operating system CSPRNG, typically [`OsRng`](super::OsRng).
/// * **Long-running processes.** Per SP 800-90A §10.1.2.4 the DRBG must
///   be reseeded before the counter exceeds `2^48` draws; this type
///   panics on overflow but the caller is responsible for *invoking*
///   [`reseed`](Self::reseed) with fresh entropy before that point.
///
/// For one-shot key generation, signatures, and other transient secrets
/// in a single-process context, [`OsRng`](super::OsRng) is the simpler
/// choice. Reach for `HmacDrbg` when you specifically need determinism.
#[derive(Clone)]
pub struct HmacDrbg<D: Digest> {
    /// HMAC key.
    k: D::Output,
    /// Internal value updated on each output block.
    v: D::Output,
    reseed_counter: u64,
}

/// SP 800-90A §10.1.2.4 reseed interval for HMAC-DRBG: `2^48` requests.
const RESEED_INTERVAL: u64 = 1u64 << 48;

/// SP 800-90A Table 2 `max_number_of_bits_per_request` for HMAC-DRBG:
/// `2^19` bits = 65_536 bytes. [`HmacDrbg::generate`] satisfies larger
/// fills by internally splitting them into multiple requests (ratcheting
/// K/V between chunks), so callers never see the limit.
const MAX_BYTES_PER_REQUEST: usize = 1 << 16;

impl<D: Digest> HmacDrbg<D> {
    /// Instantiates the DRBG from `entropy`, a `nonce`, and an optional
    /// `personalization` string (`nonce` / `personalization` may be empty;
    /// `entropy` must not be).
    ///
    /// # Panics
    /// Panics if `entropy` is empty: an entropy-free instantiation makes
    /// every output of the generator predictable.
    pub fn new(entropy: &[u8], nonce: &[u8], personalization: &[u8]) -> Self {
        assert!(
            !entropy.is_empty(),
            "HMAC-DRBG requires non-empty entropy input (SP 800-90A §8.6.5)"
        );
        // K = 0x00…, V = 0x01…
        let k = D::zeroed_output();
        let mut v = D::zeroed_output();
        for b in v.as_mut() {
            *b = 0x01;
        }
        let mut drbg = HmacDrbg {
            k,
            v,
            reseed_counter: 1,
        };
        drbg.update(&[entropy, nonce, personalization]);
        drbg
    }

    /// The SP 800-90A `HMAC_DRBG_Update` step over the concatenation of
    /// `provided`.
    fn update(&mut self, provided: &[&[u8]]) {
        // K = HMAC(K, V || 0x00 || provided)
        let mut mac = Hmac::<D>::new(self.k.as_ref());
        mac.update(self.v.as_ref());
        mac.update(&[0x00]);
        for p in provided {
            mac.update(p);
        }
        self.k = mac.finalize();
        // V = HMAC(K, V)
        self.v = Hmac::<D>::mac(self.k.as_ref(), self.v.as_ref());

        // The second pass runs only when there was provided data.
        if provided.iter().any(|p| !p.is_empty()) {
            let mut mac = Hmac::<D>::new(self.k.as_ref());
            mac.update(self.v.as_ref());
            mac.update(&[0x01]);
            for p in provided {
                mac.update(p);
            }
            self.k = mac.finalize();
            self.v = Hmac::<D>::mac(self.k.as_ref(), self.v.as_ref());
        }
    }

    /// Reseeds with fresh `entropy` and optional `additional` input.
    pub fn reseed(&mut self, entropy: &[u8], additional: &[u8]) {
        self.update(&[entropy, additional]);
        self.reseed_counter = 1;
    }

    /// Fills `out` with generated bytes, optionally mixing in `additional`
    /// input.
    ///
    /// SP 800-90A Table 2 caps a single HMAC-DRBG request at `2^19` bits
    /// (65_536 bytes). Larger `out` buffers are transparently served as a
    /// sequence of maximum-size requests — the K/V state ratchets through
    /// the standard update step between chunks (and the reseed counter
    /// advances once per chunk), so no single request exceeds the bound.
    /// `additional` input is mixed into the first chunk only. Requests of
    /// at most 65_536 bytes produce byte-identical output to previous
    /// releases.
    ///
    /// # Panics
    /// Panics if the per-instance request count would exceed SP 800-90A's
    /// reseed interval (`2^48`). Call [`reseed`](Self::reseed) before that
    /// happens.
    pub fn generate(&mut self, out: &mut [u8], additional: &[u8]) {
        if out.len() <= MAX_BYTES_PER_REQUEST {
            self.generate_request(out, additional);
            return;
        }
        let mut first = true;
        for chunk in out.chunks_mut(MAX_BYTES_PER_REQUEST) {
            self.generate_request(chunk, if first { additional } else { &[] });
            first = false;
        }
    }

    /// One SP 800-90A `HMAC_DRBG_Generate` request (`out` must satisfy
    /// `max_number_of_bits_per_request`; [`generate`](Self::generate)
    /// enforces that by splitting).
    fn generate_request(&mut self, out: &mut [u8], additional: &[u8]) {
        debug_assert!(out.len() <= MAX_BYTES_PER_REQUEST);
        assert!(
            self.reseed_counter < RESEED_INTERVAL,
            "HMAC-DRBG reseed interval exceeded (SP 800-90A §10.1.2.4); call reseed() first"
        );
        if !additional.is_empty() {
            self.update(&[additional]);
        }

        let mut filled = 0;
        while filled < out.len() {
            self.v = Hmac::<D>::mac(self.k.as_ref(), self.v.as_ref());
            let block = self.v.as_ref();
            let n = (out.len() - filled).min(block.len());
            out[filled..filled + n].copy_from_slice(&block[..n]);
            filled += n;
        }

        self.update(&[additional]);
        self.reseed_counter += 1;
    }
}

impl<D: Digest> Drop for HmacDrbg<D> {
    fn drop(&mut self) {
        // Wipe the secret HMAC key and chaining value so they do not linger
        // in freed memory. Mirrors the `black_box`-fenced overwrite used by
        // `HmacPrf` in `kdf::kbkdf`.
        for b in self.k.as_mut() {
            *b = 0;
        }
        for b in self.v.as_mut() {
            *b = 0;
        }
        let _ = core::hint::black_box(self.k.as_ref());
        let _ = core::hint::black_box(self.v.as_ref());
    }
}

impl<D: Digest> RngCore for HmacDrbg<D> {
    #[inline]
    fn fill_bytes(&mut self, dest: &mut [u8]) {
        self.generate(dest, &[]);
    }
}

impl<D: Digest> CryptoRng for HmacDrbg<D> {}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::hash::Sha256;
    use crate::test_util::from_hex;

    #[test]
    fn nist_hmac_drbg_sha256() {
        // NIST CAVP HMAC_DRBG SHA-256, no reseed / no prediction resistance,
        // empty personalization & additional input, COUNT 0. Two 1024-bit
        // generate calls; the second is the known answer.
        let entropy =
            from_hex::<32>("ca851911349384bffe89de1cbdc46e6831e44d34a4fb935ee285dd14b71a7488");
        let nonce = from_hex::<16>("659ba96c601dc69fc902940805ec0ca8");
        let expected = from_hex::<128>(
            "e528e9abf2dece54d47c7e75e5fe302149f817ea9fb4bee6f4199697d04d5b89\
             d54fbb978a15b5c443c9ec21036d2460b6f73ebad0dc2aba6e624abf07745bc1\
             07694bb7547bb0995f70de25d6b29e2d3011bb19d27676c07162c8b5ccde0668\
             961df86803482cb37ed6d5c0bb8d50cf1f50d476aa0458bdaba806f48be9dcb8",
        );

        let mut drbg = HmacDrbg::<Sha256>::new(&entropy, &nonce, &[]);
        let mut buf = [0u8; 128];
        drbg.generate(&mut buf, &[]); // discarded
        drbg.generate(&mut buf, &[]);
        assert_eq!(buf, expected);
    }

    #[test]
    fn deterministic_and_seed_sensitive() {
        let mk = |seed: &[u8]| {
            let mut d = HmacDrbg::<Sha256>::new(seed, b"nonce", &[]);
            let mut o = [0u8; 48];
            d.fill_bytes(&mut o);
            o
        };
        assert_eq!(mk(b"seed-a"), mk(b"seed-a"));
        assert_ne!(mk(b"seed-a"), mk(b"seed-b"));
    }

    #[test]
    fn multiblock_generate_is_deterministic() {
        // 100 bytes spans four SHA-256 output blocks, exercising block
        // stitching within a single request; two equally seeded generators
        // must agree byte-for-byte.
        let mut a = HmacDrbg::<Sha256>::new(b"entropy-source", b"nonce", &[]);
        let mut b = HmacDrbg::<Sha256>::new(b"entropy-source", b"nonce", &[]);
        let mut whole = [0u8; 100];
        let mut whole2 = [0u8; 100];
        a.fill_bytes(&mut whole);
        b.fill_bytes(&mut whole2);
        assert_ne!(whole, [0u8; 100]);
        assert_eq!(whole, whole2);
    }

    #[test]
    fn reseed_changes_stream() {
        let mut a = HmacDrbg::<Sha256>::new(b"seed", b"nonce", &[]);
        let mut b = HmacDrbg::<Sha256>::new(b"seed", b"nonce", &[]);
        b.reseed(b"more-entropy", &[]);
        let (mut x, mut y) = ([0u8; 32], [0u8; 32]);
        a.fill_bytes(&mut x);
        b.fill_bytes(&mut y);
        assert_ne!(x, y);
    }

    /// Empty entropy is rejected at instantiation.
    #[test]
    #[should_panic(expected = "non-empty entropy")]
    fn empty_entropy_rejected() {
        let _ = HmacDrbg::<Sha256>::new(b"", b"nonce", b"pers");
    }

    /// A fill larger than SP 800-90A's 65_536-byte per-request cap is
    /// served as a sequence of capped requests: it must equal issuing the
    /// chunked requests by hand, and the reseed counter advances once per
    /// internal request.
    #[test]
    fn oversized_fill_splits_into_capped_requests() {
        let mut a = HmacDrbg::<Sha256>::new(b"entropy", b"nonce", &[]);
        let mut b = HmacDrbg::<Sha256>::new(b"entropy", b"nonce", &[]);

        const TOTAL: usize = MAX_BYTES_PER_REQUEST + 1000;
        let mut whole = [0u8; TOTAL];
        a.generate(&mut whole, b"addl");

        let mut parts = [0u8; TOTAL];
        let (first, rest) = parts.split_at_mut(MAX_BYTES_PER_REQUEST);
        b.generate(first, b"addl");
        b.generate(rest, &[]);

        assert_eq!(whole, parts);
        // Two internal requests each: counters must agree and have advanced.
        assert_eq!(a.reseed_counter, b.reseed_counter);
        assert_eq!(a.reseed_counter, 3); // instantiated at 1, +2 requests
    }

    /// Requests at or below the cap are a single request — output and
    /// counter behaviour are unchanged from previous releases.
    #[test]
    fn at_cap_fill_is_single_request() {
        let mut a = HmacDrbg::<Sha256>::new(b"entropy", b"nonce", &[]);
        let mut buf = [0u8; MAX_BYTES_PER_REQUEST];
        a.generate(&mut buf, &[]);
        assert_eq!(a.reseed_counter, 2);
    }

    #[test]
    fn drops_cleanly_after_use() {
        // The Drop impl zeroizes the secret key and chaining value; this
        // exercises that path (no use-after-free / panic) and documents that
        // an instantiated, used DRBG can be dropped. Correctness of the wipe
        // itself cannot be observed once the memory is freed.
        let mut d = HmacDrbg::<Sha256>::new(b"entropy", b"nonce", b"pers");
        let mut o = [0u8; 32];
        d.fill_bytes(&mut o);
        drop(d);
    }
}