Skip to content

Commit

Permalink
Port ReseedingRng changes
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Feb 2, 2018
1 parent 485704b commit 49637d9
Showing 1 changed file with 134 additions and 33 deletions.
167 changes: 134 additions & 33 deletions src/reseeding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,59 @@
// 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 PRNG's can have very small periods of for
/// example less than 2<sup>64</sup>. Would reseeding help to ensure that you do
/// not wrap around at the end of the period? A period of 2<sup>64</sup> 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 there is found a cryptographic weakness in the used CSPRNG,
/// 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 seemingly
/// strong, does not come with a security proof and does not meet the current
/// standards for a cryptographically secure PRNG. By reseeding it very
/// 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 methods `ReseedingRng` wil not panic, but try to handle the
/// error intelligently. And if nothing helps, continue 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 handeling strategy.
///
/// Use `try_fill_bytes` and possible `try_reseed` if you want to handle
/// reseeding errors explicitly. All reseeding errors will either be
/// `ErrorKind::Transient` or `ErrorKind::NotReady`, and contain the original
/// error as cause.
#[derive(Debug)]
pub struct ReseedingRng<R, Rsdr> {
rng: R,
generation_threshold: u64,
bytes_generated: u64,
/// Controls the behaviour when reseeding the RNG.
reseeder: Rsdr,
threshold: i64,
bytes_until_reseed: i64,
}

impl<R: Rng+SeedableRng, Rsdr: Rng> ReseedingRng<R, Rsdr> {
Expand All @@ -30,52 +69,114 @@ impl<R: Rng+SeedableRng, Rsdr: Rng> ReseedingRng<R, Rsdr> {
/// # 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<R,Rsdr> {
/// * `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<R,Rsdr> {
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 RNG.
///
/// On error, this will try to intelligently handle reseeding. If the error
/// kind indicates retrying might help, it will immidiately retry a couple
/// of times. If the error kind indicates the seeding RNG is not ready, it
/// will retry after a while, after `threshold / 256` generated bytes.
///
/// If the seeding RNG has an other error or a permanently failure, it will
/// completely skip reseeding. Only after generating `threshold` bytes it
/// will retry again.
#[inline(never)]
pub fn reseed(&mut self) {
trace!("Reseeding RNG after {} generated 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;
info!("Reseeding delayed, retrying after {} generated 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; }
}
info!("Reseeding failed, RNG remains unchanged. 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();
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.
#[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<R: Rng+SeedableRng, Rsdr: Rng> Rng for ReseedingRng<R, Rsdr> {
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(())
}
}

Expand Down

0 comments on commit 49637d9

Please sign in to comment.