diff --git a/benches/generators.rs b/benches/generators.rs index 044d7b24e89..0298f5ed65f 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -9,8 +9,9 @@ const BYTES_LEN: usize = 1024; use std::mem::size_of; use test::{black_box, Bencher}; -use rand::{Rng, NewRng, StdRng, OsRng, JitterRng}; +use rand::{Rng, NewRng, StdRng, OsRng, JitterRng, EntropyRng}; use rand::{XorShiftRng, Hc128Rng, IsaacRng, Isaac64Rng, ChaChaRng}; +use rand::reseeding::ReseedingRng; macro_rules! gen_bytes { ($fnn:ident, $gen:ident) => { @@ -162,3 +163,39 @@ macro_rules! chacha_rounds { chacha_rounds!(gen_bytes_chacha8, gen_u32_chacha8, gen_u64_chacha8, 8); chacha_rounds!(gen_bytes_chacha12, gen_u32_chacha12, gen_u64_chacha12, 12); chacha_rounds!(gen_bytes_chacha20, gen_u32_chacha20, gen_u64_chacha20, 20); + + +#[bench] +fn reseeding_hc128_bytes(b: &mut Bencher) { + let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(), + 128*1024*1024, + EntropyRng::new()); + let mut buf = [0u8; BYTES_LEN]; + b.iter(|| { + for _ in 0..RAND_BENCH_N { + rng.fill_bytes(&mut buf); + black_box(buf); + } + }); + b.bytes = BYTES_LEN as u64 * RAND_BENCH_N; +} + +macro_rules! reseeding_uint { + ($fnn:ident, $ty:ty) => { + #[bench] + fn $fnn(b: &mut Bencher) { + let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(), + 128*1024*1024, + EntropyRng::new()); + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(rng.gen::<$ty>()); + } + }); + b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; + } + } +} + +reseeding_uint!(reseeding_hc128_u32, u32); +reseeding_uint!(reseeding_hc128_u64, u64); diff --git a/src/lib.rs b/src/lib.rs index 20941633d2b..e0d0a68dcf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -258,7 +258,7 @@ #[cfg(not(feature = "log"))] macro_rules! trace { ($($x:tt)*) => () } #[cfg(not(feature = "log"))] macro_rules! debug { ($($x:tt)*) => () } #[cfg(all(feature="std", not(feature = "log")))] macro_rules! info { ($($x:tt)*) => () } -#[cfg(all(feature="std", not(feature = "log")))] macro_rules! warn { ($($x:tt)*) => () } +#[cfg(not(feature = "log"))] macro_rules! warn { ($($x:tt)*) => () } #[cfg(all(feature="std", not(feature = "log")))] macro_rules! error { ($($x:tt)*) => () } @@ -1284,7 +1284,7 @@ mod test { 80, 81, 82, 83, 84, 85, 86, 87]; for &n in lengths.iter() { let mut buffer = [0u8; 87]; - let mut v = &mut buffer[0..n]; + let v = &mut buffer[0..n]; r.fill_bytes(v); // use this to get nicer error messages. diff --git a/src/reseeding.rs b/src/reseeding.rs index cf47b3fea8d..0fe85e4028f 100644 --- a/src/reseeding.rs +++ b/src/reseeding.rs @@ -8,20 +8,58 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! A wrapper around another RNG that reseeds it after it +//! A wrapper around another PRNG that reseeds it after it //! generates a certain number of random bytes. -use {Rng, SeedableRng, Error}; - -/// A wrapper around any RNG which reseeds the underlying RNG after it -/// has generated a certain number of random bytes. +use {Rng, SeedableRng, Error, ErrorKind}; + +/// A wrapper around any PRNG which reseeds the underlying PRNG after it has +/// generated a certain number of random bytes. +/// +/// Reseeding is never strictly *necessary*. Cryptographic PRNGs don't have a +/// limited number of bytes they can output, or at least not a limit reachable +/// in any practical way. There is no such thing as 'running out of entropy'. +/// +/// Some small non-cryptographic PRNGs can have very small periods, for +/// example less than 264. Would reseeding help to ensure that you do +/// not wrap around at the end of the period? A period of 264 still +/// takes several centuries of CPU-years on current hardware. Reseeding will +/// actually make things worse, because the reseeded PRNG will just continue +/// somewhere else *in the same period*, with a high chance of overlapping with +/// previously used parts of it. +/// +/// # When should you use `ReseedingRng`? +/// +/// - Reseeding can be seen as some form of 'security in depth'. Even if in the +/// future a cryptographic weakness is found in the CSPRNG being used, +/// occasionally reseeding should make exploiting it much more difficult or +/// even impossible. +/// - It can be used as a poor man's cryptography (not recommended, just use a +/// good CSPRNG). Previous implementations of `thread_rng` for example used +/// `ReseedingRng` with the ISAAC RNG. That algorithm, although apparently +/// strong and with no known attack, does not come with any proof of security +/// and does not meet the current standards for a cryptographically secure +/// PRNG. By reseeding it frequently (every 32 MiB) it seems safe to assume +/// there is no attack that can operate on the tiny window between reseeds. +/// +/// # Error handling +/// +/// If reseeding fails, `try_fill_bytes` is the only `Rng` method to report it. +/// For all other `Rng` methods, `ReseedingRng` will not panic but try to +/// handle the error intelligently; if handling the source error fails these +/// methods will continue generating data from the wrapped PRNG without +/// reseeding. +/// +/// It is usually best to use the infallible methods `next_u32`, `next_u64` and +/// `fill_bytes` because they can make use of this error handling strategy. +/// Use `try_fill_bytes` and possibly `try_reseed` if you want to handle +/// reseeding errors explicitly. #[derive(Debug)] pub struct ReseedingRng { rng: R, - generation_threshold: u64, - bytes_generated: u64, - /// Controls the behaviour when reseeding the RNG. reseeder: Rsdr, + threshold: i64, + bytes_until_reseed: i64, } impl ReseedingRng { @@ -30,53 +68,115 @@ impl ReseedingRng { /// # Arguments /// /// * `rng`: the random number generator to use. - /// * `generation_threshold`: the number of bytes of entropy at which to reseed the RNG. - /// * `reseeder`: the reseeding object to use. - pub fn new(rng: R, generation_threshold: u64, reseeder: Rsdr) -> ReseedingRng { + /// * `threshold`: the number of generated bytes after which to reseed the RNG. + /// * `reseeder`: the RNG to use for reseeding. + pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> ReseedingRng { + assert!(threshold <= ::core::i64::MAX as u64); ReseedingRng { rng: rng, - generation_threshold: generation_threshold, - bytes_generated: 0, - reseeder: reseeder + reseeder: reseeder, + threshold: threshold as i64, + bytes_until_reseed: threshold as i64, + } + } + + /// Reseed the internal PRNG. + /// + /// This will try to work around errors in the RNG used for reseeding + /// intelligently. If the error kind indicates retrying might help, it will + /// immediately retry a couple of times. If the error kind indicates the + /// seeding RNG is not ready, it will retry later, after `threshold / 256` + /// generated bytes. On other errors in the source RNG, this will skip + /// reseeding and continue using the internal PRNG, until another + /// `threshold` bytes have been generated (at which point it will try + /// reseeding again). + #[inline(never)] + pub fn reseed(&mut self) { + trace!("Reseeding RNG after generating {} bytes", + self.threshold - self.bytes_until_reseed); + self.bytes_until_reseed = self.threshold; + let mut err_count = 0; + loop { + if let Err(e) = R::from_rng(&mut self.reseeder) + .map(|result| self.rng = result) { + let kind = e.kind(); + if kind.should_wait() { + self.bytes_until_reseed = self.threshold >> 8; + warn!("Reseeding RNG delayed for {} bytes", + self.bytes_until_reseed); + } else if kind.should_retry() { + err_count += 1; + // Retry immediately for 5 times (arbitrary limit) + if err_count <= 5 { continue; } + } + warn!("Reseeding RNG failed; continuing without reseeding. Error: {}", e); + } + break; // Successfully reseeded, delayed, or given up. } } /// Reseed the internal RNG if the number of bytes that have been /// generated exceed the threshold. - pub fn reseed_if_necessary(&mut self) { - if self.bytes_generated >= self.generation_threshold { - trace!("Reseeding RNG after {} bytes", self.bytes_generated); - R::from_rng(&mut self.reseeder).map(|result| self.rng = result) - .unwrap_or_else(|err| panic!("reseeding failed: {}", err)); - self.bytes_generated = 0; + /// + /// If reseeding fails, return an error with the original cause. Note that + /// if the cause has a permanent failure, we report a transient error and + /// skip reseeding; this means that only two error kinds can be reported + /// from this method: `ErrorKind::Transient` and `ErrorKind::NotReady`. + #[inline(never)] + pub fn try_reseed(&mut self) -> Result<(), Error> { + trace!("Reseeding RNG after {} generated bytes", + self.threshold - self.bytes_until_reseed); + if let Err(err) = R::from_rng(&mut self.reseeder) + .map(|result| self.rng = result) { + let newkind = match err.kind() { + a @ ErrorKind::NotReady => a, + b @ ErrorKind::Transient => b, + _ => { + self.bytes_until_reseed = self.threshold; // skip reseeding + ErrorKind::Transient + } + }; + return Err(Error::with_cause(newkind, "reseeding failed", err)); } + self.bytes_until_reseed = self.threshold; + Ok(()) } } - impl Rng for ReseedingRng { fn next_u32(&mut self) -> u32 { - self.reseed_if_necessary(); - self.bytes_generated += 4; - self.rng.next_u32() + let value = self.rng.next_u32(); + self.bytes_until_reseed -= 4; + if self.bytes_until_reseed <= 0 { + self.reseed(); + } + value } fn next_u64(&mut self) -> u64 { - self.reseed_if_necessary(); - self.bytes_generated += 8; - self.rng.next_u64() + let value = self.rng.next_u64(); + self.bytes_until_reseed -= 8; + if self.bytes_until_reseed <= 0 { + self.reseed(); + } + value } fn fill_bytes(&mut self, dest: &mut [u8]) { - self.reseed_if_necessary(); - self.bytes_generated += dest.len() as u64; - self.rng.fill_bytes(dest) + self.rng.fill_bytes(dest); + self.bytes_until_reseed -= dest.len() as i64; + if self.bytes_until_reseed <= 0 { + self.reseed(); + } } - + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.reseed_if_necessary(); - self.bytes_generated += dest.len() as u64; - self.rng.try_fill_bytes(dest) + self.rng.try_fill_bytes(dest)?; + self.bytes_until_reseed -= dest.len() as i64; + if self.bytes_until_reseed <= 0 { + self.try_reseed()?; + } + Ok(()) } }