Skip to content

Commit

Permalink
Use BlockRng in ReseedingRng
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Mar 3, 2018
1 parent af8bbd7 commit 11af2b0
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 104 deletions.
12 changes: 8 additions & 4 deletions benches/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use test::{black_box, Bencher};
use rand::{RngCore, Rng, SeedableRng, NewRng, StdRng, OsRng, JitterRng, EntropyRng};
use rand::{XorShiftRng, Hc128Rng, IsaacRng, Isaac64Rng, ChaChaRng};
use rand::reseeding::ReseedingRng;
use rand::prng::hc128::Hc128Core;
use rand::thread_rng;

macro_rules! gen_bytes {
Expand Down Expand Up @@ -151,10 +152,13 @@ chacha_rounds!(gen_bytes_chacha12, gen_u32_chacha12, gen_u64_chacha12, 12);
chacha_rounds!(gen_bytes_chacha20, gen_u32_chacha20, gen_u64_chacha20, 20);


const RESEEDING_THRESHOLD: u64 = 1024*1024*1024; // something high enough to get
// deterministic measurements

#[bench]
fn reseeding_hc128_bytes(b: &mut Bencher) {
let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(),
128*1024*1024,
let mut rng = ReseedingRng::new(Hc128Core::new().unwrap(),
RESEEDING_THRESHOLD,
EntropyRng::new());
let mut buf = [0u8; BYTES_LEN];
b.iter(|| {
Expand All @@ -170,8 +174,8 @@ 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,
let mut rng = ReseedingRng::new(Hc128Core::new().unwrap(),
RESEEDING_THRESHOLD,
EntropyRng::new());
b.iter(|| {
for _ in 0..RAND_BENCH_N {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ pub use jitter::JitterRng;
#[cfg(feature="std")] pub use os::OsRng;

// pseudo rngs
pub mod prng;
pub use isaac::{IsaacRng, Isaac64Rng};
pub use chacha::ChaChaRng;
pub use prng::XorShiftRng;
Expand Down Expand Up @@ -317,7 +318,6 @@ pub mod isaac {
mod le;
#[cfg(feature="std")] mod entropy_rng;
mod error;
mod prng;
#[cfg(feature="std")] mod thread_rng;


Expand Down
6 changes: 3 additions & 3 deletions src/prng/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
//! same algorithm, it is possible that both will yield the same sequence of
//! values (with some lag).

mod chacha;
mod hc128;
pub mod chacha;
pub mod hc128;
mod isaac;
mod isaac64;
mod xorshift;
Expand All @@ -53,4 +53,4 @@ pub use self::chacha::ChaChaRng;
pub use self::hc128::Hc128Rng;
pub use self::isaac::IsaacRng;
pub use self::isaac64::Isaac64Rng;
pub use self::xorshift::XorShiftRng;
pub use self::xorshift::XorShiftRng;
193 changes: 106 additions & 87 deletions src/reseeding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
//! A wrapper around another PRNG that reseeds it after it
//! generates a certain number of random bytes.

use {RngCore, SeedableRng, Error, ErrorKind};
use {RngCore, BlockRngCore, SeedableRng, Error, ErrorKind};
use impls::BlockRng;

/// A wrapper around any PRNG which reseeds the underlying PRNG after it has
/// generated a certain number of random bytes.
Expand Down Expand Up @@ -39,73 +40,125 @@ use {RngCore, SeedableRng, Error, ErrorKind};
/// `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
/// PRNG. By reseeding it frequently (every 32 kiB) 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
/// handle the error intelligently through some combination of retrying and
/// delaying reseeding until later. 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,
reseeder: Rsdr,
threshold: i64,
bytes_until_reseed: i64,
}
pub struct ReseedingRng<R, Rsdr>(BlockRng<Reseeder<R, Rsdr>>)
where R: BlockRngCore<u32> + SeedableRng,
Rsdr: RngCore;

impl<R: RngCore + SeedableRng, Rsdr: RngCore> ReseedingRng<R, Rsdr> {
impl<R, Rsdr> ReseedingRng<R, Rsdr>
where R: BlockRngCore<u32> + SeedableRng,
Rsdr: RngCore
{
/// Create a new `ReseedingRng` with the given parameters.
///
/// # Arguments
///
/// * `rng`: the random number generator to use.
/// * `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> {
pub fn new(rng: R, threshold: u64, reseeder: Rsdr)
-> ReseedingRng<R, Rsdr>
{
assert!(threshold <= ::core::i64::MAX as u64);
ReseedingRng {
rng: rng,
reseeder: reseeder,
threshold: threshold as i64,
bytes_until_reseed: threshold as i64,
let results_empty = R::Results::default();
ReseedingRng(
BlockRng {
core: Reseeder {
core: rng,
reseeder: reseeder,
threshold: threshold as i64,
bytes_until_reseed: threshold as i64,
},
index: results_empty.as_ref().len(), // generate on first use
results: results_empty,
}
)
}

/// Reseed the internal PRNG.
pub fn reseed(&mut self) -> Result<(), Error> {
self.0.core.reseed()
}
}

impl<R: BlockRngCore<u32> + SeedableRng, Rsdr: RngCore> RngCore for ReseedingRng<R, Rsdr> {
#[inline]
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}

#[inline]
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}

#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.fill_bytes(dest)
}

#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.0.try_fill_bytes(dest)
}
}

#[derive(Debug)]
struct Reseeder<R, Rsdr> {
core: R,
reseeder: Rsdr,
threshold: i64,
bytes_until_reseed: i64,
}

impl<R, Rsdr> BlockRngCore<u32> for Reseeder<R, Rsdr>
where R: BlockRngCore<u32> + SeedableRng,
Rsdr: RngCore
{
type Results = <R as BlockRngCore<u32>>::Results;

fn generate(&mut self, results: &mut Self::Results) -> Result<(), Error> {
if self.bytes_until_reseed <= 0 {
// In the unlikely event the internal PRNG fails, we don't know
// whether this is resolvable; schedule to reseed on next use and
// return original error kind.
return self.reseed_and_generate(results);
}
self.bytes_until_reseed -= results.as_ref().len() as i64 * 4;
self.core.generate(results)
}
}

impl<R, Rsdr> Reseeder<R, Rsdr>
where R: BlockRngCore<u32> + SeedableRng,
Rsdr: RngCore
{
/// Reseed the internal PRNG.
///
/// This will try to work around errors in the RNG used for reseeding
/// intelligently through some combination of retrying and delaying
/// reseeding until later. So long as the internal PRNG doesn't fail, this
/// method will not fail, i.e. failures from the reseeding source are not
/// fatal.
pub fn reseed(&mut self) {
// Behaviour is identical to `try_reseed`; we just squelch the error.
let _res = self.try_reseed();
fn reseed(&mut self) -> Result<(), Error> {
R::from_rng(&mut self.reseeder).map(|result| self.core = result)
}

/// Reseed the internal RNG if the number of bytes that have been
/// generated exceed the threshold.
/// Reseed the internal PRNG.
///
/// If reseeding fails, return an error with the original cause. Note that
/// in case of error we simply delay reseeding, allowing the generator to
/// continue its output of random data and try reseeding again later;
/// because of this we always return kind `ErrorKind::Transient`.
#[inline(never)]
pub fn try_reseed(&mut self) -> Result<(), Error> {
/// If reseeding fails, this will try to work around errors intelligently
/// through some combination of retrying and delaying reseeding until later.
/// It will also report the error with `ErrorKind::Transient` with the
/// original error as cause.
fn auto_reseed(&mut self) -> Result<(), Error> {
trace!("Reseeding RNG after {} generated bytes",
self.threshold - self.bytes_until_reseed);
if let Err(mut e) = R::from_rng(&mut self.reseeder)
.map(|result| self.rng = result)
{
if let Err(mut e) = self.reseed() {
let delay = match e.kind {
ErrorKind::Transient => 0,
kind @ _ if kind.should_retry() => self.threshold >> 8,
Expand All @@ -121,64 +174,30 @@ impl<R: RngCore + SeedableRng, Rsdr: RngCore> ReseedingRng<R, Rsdr> {
Ok(())
}
}
}

impl<R: RngCore + SeedableRng, Rsdr: RngCore> RngCore for ReseedingRng<R, Rsdr> {
fn next_u32(&mut self) -> 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 {
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.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> {
let res1 = self.rng.try_fill_bytes(dest);
self.bytes_until_reseed -= dest.len() as i64;
let res2 = if self.bytes_until_reseed <= 0 {
self.try_reseed()
} else { Ok(()) };

if let Err(e) = res1 {
// In the unlikely event the internal PRNG fails, we don't know
// whether this is resolvable; reseed immediately and return
// original error kind.
self.bytes_until_reseed = 0;
Err(e)
} else {
res2
}
#[inline(never)]
fn reseed_and_generate(&mut self,
results: &mut <Self as BlockRngCore<u32>>::Results)
-> Result<(), Error>
{
let res1 = self.auto_reseed();
self.bytes_until_reseed -= results.as_ref().len() as i64 * 4;
let res2 = self.core.generate(results);
if res2.is_err() { res2 } else { res1 }
}
}

#[cfg(test)]
mod test {
use {Rng, SeedableRng, StdRng};
use {Rng, SeedableRng};
use prng::chacha::ChaChaCore;
use mock::StepRng;
use super::ReseedingRng;

#[test]
fn test_reseeding() {
let mut zero = StepRng::new(0, 0);
let rng = StdRng::from_rng(&mut zero).unwrap();
let rng = ChaChaCore::from_rng(&mut zero).unwrap();
let mut reseeding = ReseedingRng::new(rng, 32, zero);

// Currently we only support for arrays up to length 32.
Expand Down
20 changes: 11 additions & 9 deletions src/thread_rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
use std::cell::RefCell;
use std::rc::Rc;

use {RngCore, CryptoRng, StdRng, SeedableRng, EntropyRng};
use {RngCore, CryptoRng, SeedableRng, EntropyRng};
use prng::hc128::Hc128Core;
use {Distribution, Uniform, Rng, Error};
use reseeding::ReseedingRng;

Expand All @@ -31,13 +32,13 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 32*1024*1024; // 32 MiB
/// [`thread_rng`]: fn.thread_rng.html
#[derive(Clone, Debug)]
pub struct ThreadRng {
rng: Rc<RefCell<ReseedingRng<StdRng, EntropyRng>>>,
rng: Rc<RefCell<ReseedingRng<Hc128Core, EntropyRng>>>,
}

thread_local!(
static THREAD_RNG_KEY: Rc<RefCell<ReseedingRng<StdRng, EntropyRng>>> = {
static THREAD_RNG_KEY: Rc<RefCell<ReseedingRng<Hc128Core, EntropyRng>>> = {
let mut entropy_source = EntropyRng::new();
let r = StdRng::from_rng(&mut entropy_source).unwrap_or_else(|err|
let r = Hc128Core::from_rng(&mut entropy_source).unwrap_or_else(|err|
panic!("could not initialize thread_rng: {}", err));
let rng = ReseedingRng::new(r,
THREAD_RNG_RESEED_THRESHOLD,
Expand All @@ -51,11 +52,12 @@ thread_local!(
/// chaining style, e.g. `thread_rng().gen::<i32>()`, or cached locally, e.g.
/// `let mut rng = thread_rng();`.
///
/// `ThreadRng` uses [`ReseedingRng`] wrapping a [`StdRng`] which is reseeded
/// after generating 32 MiB of random data. A single instance is cached per
/// thread and the returned `ThreadRng` is a reference to this instance — hence
/// `ThreadRng` is neither `Send` nor `Sync` but is safe to use within a single
/// thread. This RNG is seeded and reseeded via [`EntropyRng`] as required.
/// `ThreadRng` uses [`ReseedingRng`] wrapping the same PRNG as [`StdRng`],
/// which is reseeded after generating 32 MiB of random data. A single instance
/// is cached per thread and the returned `ThreadRng` is a reference to this
/// instance — hence `ThreadRng` is neither `Send` nor `Sync` but is safe to use
/// within a single thread. This RNG is seeded and reseeded via [`EntropyRng`]
/// as required.
///
/// Note that the reseeding is done as an extra precaution against entropy
/// leaks and is in theory unnecessary — to predict `thread_rng`'s output, an
Expand Down

0 comments on commit 11af2b0

Please sign in to comment.