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(())
}
}