pub mod otp;
use rand::RngCore;
use crate::internal::getrandom;
use crate::internal::rng::ChaCha20Rng;
use crate::internal::zeroize::Zeroize;
const MAX_BYTES_BEFORE_RESEED: u64 = 1 << 24;
pub struct ChaCha20Drbg {
inner: ChaCha20Rng,
generated: u64,
}
impl ChaCha20Drbg {
pub fn from_seed(seed: [u8; 32]) -> Self {
Self {
inner: ChaCha20Rng::from_seed(seed),
generated: 0,
}
}
pub fn from_os_entropy() -> Self {
Self {
inner: ChaCha20Rng::from_entropy(),
generated: 0,
}
}
pub fn reseed(&mut self, entropy: &[u8]) {
let mut new_seed = [0u8; 32];
self.inner.fill_bytes(&mut new_seed);
for (i, &b) in entropy.iter().enumerate() {
new_seed[i % 32] ^= b;
}
self.inner = ChaCha20Rng::from_seed(new_seed);
new_seed.zeroize();
self.generated = 0;
}
pub fn random_bytes(&mut self, n: usize) -> Vec<u8> {
if self.generated + n as u64 > MAX_BYTES_BEFORE_RESEED {
let mut os_entropy = [0u8; 32];
getrandom::fill(&mut os_entropy).expect("OS entropy failed");
self.reseed(&os_entropy);
os_entropy.zeroize();
}
let mut buf = vec![0u8; n];
self.inner.fill_bytes(&mut buf);
self.generated += n as u64;
buf
}
pub fn fill(&mut self, dest: &mut [u8]) {
let bytes = self.random_bytes(dest.len());
dest.copy_from_slice(&bytes);
}
}
impl Drop for ChaCha20Drbg {
fn drop(&mut self) {
let mut zeros = [0u8; 32];
self.inner.fill_bytes(&mut zeros);
zeros.zeroize();
}
}
impl std::fmt::Debug for ChaCha20Drbg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ChaCha20Drbg")
.field("generated", &self.generated)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic_from_seed() {
let seed = [42u8; 32];
let mut a = ChaCha20Drbg::from_seed(seed);
let mut b = ChaCha20Drbg::from_seed(seed);
assert_eq!(a.random_bytes(64), b.random_bytes(64));
}
#[test]
fn different_seeds_different_output() {
let mut a = ChaCha20Drbg::from_seed([1u8; 32]);
let mut b = ChaCha20Drbg::from_seed([2u8; 32]);
assert_ne!(a.random_bytes(32), b.random_bytes(32));
}
#[test]
fn reseed_changes_output() {
let seed = [42u8; 32];
let mut drbg = ChaCha20Drbg::from_seed(seed);
let before = drbg.random_bytes(32);
let mut drbg2 = ChaCha20Drbg::from_seed(seed);
drbg2.reseed(b"extra entropy here!!");
let after = drbg2.random_bytes(32);
assert_ne!(before, after);
}
}