use super::{CryptoRng, RngCore};
use crate::hash::{Digest, Hmac};
#[derive(Clone)]
pub struct HmacDrbg<D: Digest> {
k: D::Output,
v: D::Output,
reseed_counter: u64,
}
const RESEED_INTERVAL: u64 = 1u64 << 48;
const MAX_BYTES_PER_REQUEST: usize = 1 << 16;
impl<D: Digest> HmacDrbg<D> {
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)"
);
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
}
fn update(&mut self, provided: &[&[u8]]) {
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();
self.v = Hmac::<D>::mac(self.k.as_ref(), self.v.as_ref());
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());
}
}
pub fn reseed(&mut self, entropy: &[u8], additional: &[u8]) {
self.update(&[entropy, additional]);
self.reseed_counter = 1;
}
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;
}
}
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) {
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() {
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, &[]); 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() {
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);
}
#[test]
#[should_panic(expected = "non-empty entropy")]
fn empty_entropy_rejected() {
let _ = HmacDrbg::<Sha256>::new(b"", b"nonce", b"pers");
}
#[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);
assert_eq!(a.reseed_counter, b.reseed_counter);
assert_eq!(a.reseed_counter, 3); }
#[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() {
let mut d = HmacDrbg::<Sha256>::new(b"entropy", b"nonce", b"pers");
let mut o = [0u8; 32];
d.fill_bytes(&mut o);
drop(d);
}
}