use crate::Error;
use crate::aes::{Aes256, CTR_BATCH_BLOCKS};
const SEEDLEN: usize = 48;
const MAX_REQUEST: usize = 65536;
const RESEED_INTERVAL: u64 = 1u64 << 48;
pub struct CtrDrbg {
aes: Aes256,
key: [u8; 32],
v: [u8; 16],
reseed_counter: u64,
}
impl std::fmt::Debug for CtrDrbg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CtrDrbg")
.field("reseed_counter", &self.reseed_counter)
.finish_non_exhaustive()
}
}
impl Drop for CtrDrbg {
fn drop(&mut self) {
self.key.fill(0);
self.v = [0u8; 16];
}
}
impl CtrDrbg {
pub fn new(entropy_input: &[u8], personalization: &[u8]) -> Result<Self, Error> {
if entropy_input.len() < SEEDLEN {
return Err(Error::InvalidInput);
}
let mut seed_material = Vec::with_capacity(entropy_input.len() + personalization.len());
seed_material.extend_from_slice(entropy_input);
seed_material.extend_from_slice(personalization);
let seed = xor_fold(&seed_material);
let mut drbg = Self {
aes: Aes256::new(&[0u8; 32]),
key: [0u8; 32],
v: [0u8; 16],
reseed_counter: 1,
};
drbg.update(&seed);
Ok(drbg)
}
pub fn instantiate_from_os(personalization: &[u8]) -> Result<Self, Error> {
crate::selftest::ensure_passed()?;
let mut seed = [0u8; SEEDLEN];
crate::entropy::fill(&mut seed)?;
entropy_health_tests(&seed)?;
Self::new(&seed, personalization)
}
pub fn reseed(&mut self, entropy_input: &[u8], additional_input: &[u8]) -> Result<(), Error> {
if entropy_input.len() < SEEDLEN {
return Err(Error::InvalidInput);
}
let mut seed_material = Vec::with_capacity(entropy_input.len() + additional_input.len());
seed_material.extend_from_slice(entropy_input);
seed_material.extend_from_slice(additional_input);
let seed = xor_fold(&seed_material);
self.update(&seed);
self.reseed_counter = 1;
Ok(())
}
pub fn generate(&mut self, out: &mut [u8]) -> Result<(), Error> {
self.generate_with_ai(out, None)
}
pub fn generate_with_ai(
&mut self,
out: &mut [u8],
additional_input: Option<&[u8]>,
) -> Result<(), Error> {
if out.len() > MAX_REQUEST {
return Err(Error::RngError);
}
if self.reseed_counter > RESEED_INTERVAL {
return Err(Error::RngError);
}
if let Some(ai) = additional_filter(additional_input) {
let seed = xor_fold(ai);
self.update(&seed);
}
let mut temp = Vec::with_capacity(out.len().div_ceil(16) * 16);
let mut ks = [0u8; CTR_BATCH_BLOCKS * 16];
let mut v = u128::from_be_bytes(self.v);
while temp.len() < out.len() {
v = v.wrapping_add(1);
let base = v.to_be_bytes();
let lo = u32::from_be_bytes(base[12..16].try_into().unwrap()) as u64;
let want = out.len() - temp.len();
let n = want
.div_ceil(16)
.min(CTR_BATCH_BLOCKS)
.min((0x1_0000_0000 - lo) as usize);
self.aes.encrypt_ctr_batch(base, n, &mut ks);
temp.extend_from_slice(&ks[..n * 16]);
v = v.wrapping_add(n as u128 - 1);
}
self.v = v.to_be_bytes();
out.copy_from_slice(&temp[..out.len()]);
zeroize_vec(&mut temp);
let pd = match additional_filter(additional_input) {
Some(ai) => xor_fold(ai),
None => [0u8; SEEDLEN],
};
self.update(&pd);
self.reseed_counter = self.reseed_counter.saturating_add(1);
Ok(())
}
pub fn generate_mixed(&mut self, out: &mut [u8]) -> Result<(), Error> {
crate::selftest::ensure_passed()?;
let mut ai = [0u8; 16];
crate::entropy::fill(&mut ai)?;
self.generate_with_ai(out, Some(&ai))
}
fn update(&mut self, provided_data: &[u8]) {
let mut temp = [0u8; SEEDLEN];
increment_v(&mut self.v);
let lo = u32::from_be_bytes(self.v[12..16].try_into().unwrap());
if lo <= u32::MAX - 2 {
self.aes.encrypt_ctr_batch(self.v, 3, &mut temp);
self.v = u128::from_be_bytes(self.v).wrapping_add(2).to_be_bytes();
} else {
let mut chunk = [0u8; 16];
for part in temp.chunks_mut(16) {
chunk.copy_from_slice(&self.v);
self.aes.encrypt_block(&mut chunk);
part.copy_from_slice(&chunk);
increment_v(&mut self.v);
}
}
for (i, b) in temp.iter_mut().enumerate() {
*b ^= provided_data[i];
}
self.key.copy_from_slice(&temp[..32]);
self.aes = Aes256::new(&self.key);
self.v.copy_from_slice(&temp[32..48]);
temp.fill(0);
}
}
fn xor_fold(seed_material: &[u8]) -> [u8; SEEDLEN] {
let mut out = [0u8; SEEDLEN];
for chunk in seed_material.chunks(SEEDLEN) {
for (i, b) in chunk.iter().enumerate() {
out[i] ^= b;
}
}
out
}
fn additional_filter(ai: Option<&[u8]>) -> Option<&[u8]> {
match ai {
Some(b) if !b.is_empty() => Some(b),
_ => None,
}
}
fn increment_v(v: &mut [u8; 16]) {
for i in (0..16).rev() {
let (nv, ov) = v[i].overflowing_add(1);
v[i] = nv;
if !ov {
break;
}
}
}
fn zeroize_vec(v: &mut [u8]) {
for b in v.iter_mut() {
*b = 0;
}
}
fn entropy_health_tests(seed: &[u8; SEEDLEN]) -> Result<(), Error> {
let mut run = 1u8;
for i in 1..seed.len() {
if seed[i] == seed[i - 1] {
run += 1;
if run >= 3 {
return Err(Error::EntropyFailed);
}
} else {
run = 1;
}
}
let first = seed[0];
if seed.iter().filter(|&&b| b == first).count() >= 6 {
return Err(Error::EntropyFailed);
}
Ok(())
}
#[cfg(test)]
mod health_tests {
use super::*;
#[test]
fn rct_rejects_three_identical() {
let mut seed = [0u8; SEEDLEN];
(0..48u8).for_each(|i| seed[i as usize] = i);
seed[10] = 0xAA;
seed[11] = 0xAA;
seed[12] = 0xAA;
assert_eq!(entropy_health_tests(&seed), Err(Error::EntropyFailed));
seed[12] = 0xAB;
assert!(entropy_health_tests(&seed).is_ok());
let same = [0x11u8; SEEDLEN];
assert_eq!(entropy_health_tests(&same), Err(Error::EntropyFailed));
}
#[test]
fn apt_rejects_high_repetition_of_first_byte() {
let mut seed = [0u8; SEEDLEN];
(0..48u8).for_each(|i| seed[i as usize] = i);
for s in seed.iter_mut().take(6).skip(1) {
*s = 0x00;
}
assert_eq!(entropy_health_tests(&seed), Err(Error::EntropyFailed));
}
#[test]
fn debug_output_leaks_no_state() {
let mut d = CtrDrbg::new(&[0xa5u8; SEEDLEN], b"pers").expect("instantiate");
d.generate(&mut [0u8; 32]).expect("generate");
let s = format!("{d:?}");
assert!(s.starts_with("CtrDrbg"), "unexpected Debug form: {s}");
assert!(s.contains("reseed_counter"), "unexpected Debug form: {s}");
assert!(!s.contains("key"), "Debug leaks key state: {s}");
assert!(!s.contains(" v:"), "Debug leaks V state: {s}");
assert!(!s.contains("aes"), "Debug leaks AES state: {s}");
}
}