Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port reseeding #252

Merged
merged 5 commits into from
Feb 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion benches/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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);
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)*) => () }


Expand Down Expand Up @@ -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.
Expand Down
168 changes: 134 additions & 34 deletions src/reseeding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 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<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,53 +68,115 @@ 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 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<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