use cryptography::{Hmac, Sha256};
use super::{OsRng, Rng};
const OUT: usize = 32;
pub struct HmacDrbg {
k: [u8; OUT],
v: [u8; OUT],
buf: [u8; OUT],
offset: usize,
reseed_counter: u64,
}
impl HmacDrbg {
#[must_use]
pub fn from_os_rng() -> Self {
let mut os = OsRng::new();
let mut seed = [0u8; 48]; for chunk in seed.chunks_exact_mut(4) {
chunk.copy_from_slice(&os.next_u32().to_le_bytes());
}
let mut drbg = Self {
k: [0x00u8; OUT],
v: [0x01u8; OUT],
buf: [0u8; OUT],
offset: OUT, reseed_counter: 1,
};
drbg_update(&mut drbg.k, &mut drbg.v, Some(&seed));
drbg
}
fn refill(&mut self) {
assert!(
self.reseed_counter < (1u64 << 48),
"HMAC_DRBG: reseed interval (2⁴⁸) exceeded (SP 800-90A §10.1.2 Table 2)"
);
let mac = hmac_sha256(&self.k, &self.v);
self.v.copy_from_slice(&mac);
self.buf = self.v;
drbg_update(&mut self.k, &mut self.v, None);
self.reseed_counter += 1;
self.offset = 0;
}
fn take_bytes<const N: usize>(&mut self) -> [u8; N] {
const { assert!(N <= OUT, "chunk larger than HMAC-SHA-256 output") }
if self.offset + N > OUT {
self.refill();
}
let out = self.buf[self.offset..self.offset + N].try_into().unwrap();
self.offset += N;
out
}
}
const SCRATCH: usize = 128;
fn drbg_update(k: &mut [u8; OUT], v: &mut [u8; OUT], provided_data: Option<&[u8]>) {
let pd = provided_data.unwrap_or(&[]);
debug_assert!(
OUT + 1 + pd.len() <= SCRATCH,
"drbg_update: provided_data too long"
);
let mut msg = [0u8; SCRATCH];
msg[..OUT].copy_from_slice(v);
msg[OUT] = 0x00;
msg[OUT + 1..OUT + 1 + pd.len()].copy_from_slice(pd);
let mac = hmac_sha256(k, &msg[..OUT + 1 + pd.len()]);
k.copy_from_slice(&mac);
let mac = hmac_sha256(k, v);
v.copy_from_slice(&mac);
if provided_data.is_some() {
msg[..OUT].copy_from_slice(v);
msg[OUT] = 0x01;
let mac = hmac_sha256(k, &msg[..OUT + 1 + pd.len()]);
k.copy_from_slice(&mac);
let mac = hmac_sha256(k, v);
v.copy_from_slice(&mac);
}
}
#[inline]
fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; OUT] {
let mac = Hmac::<Sha256>::compute(key, data);
mac.try_into()
.expect("HMAC-SHA-256 output is always 32 bytes")
}
impl Default for HmacDrbg {
fn default() -> Self {
Self::from_os_rng()
}
}
impl Rng for HmacDrbg {
fn next_u32(&mut self) -> u32 {
u32::from_le_bytes(self.take_bytes::<4>())
}
fn next_u64(&mut self) -> u64 {
u64::from_le_bytes(self.take_bytes::<8>())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hmac_drbg_nonzero() {
let mut rng = HmacDrbg::from_os_rng();
let v: u64 = (0..8).map(|_| rng.next_u64()).fold(0, |a, b| a | b);
assert_ne!(v, 0);
}
#[test]
fn hmac_drbg_advances() {
let mut rng = HmacDrbg::from_os_rng();
let v0 = rng.next_u64();
let v1 = rng.next_u64();
assert_ne!(v0, v1);
}
#[test]
fn hmac_drbg_update_changes_state() {
let mut a = HmacDrbg::from_os_rng();
let mut b = HmacDrbg::from_os_rng();
assert_ne!(a.next_u64(), b.next_u64());
}
}