Skip to content

Commit

Permalink
Replace StdRng algorithm with ChaCha20 and adjust reseeding threshold
Browse files Browse the repository at this point in the history
  • Loading branch information
dhardy committed Jun 3, 2019
1 parent 41b9743 commit 33bd404
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 98 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Expand Up @@ -55,7 +55,7 @@ members = [

[dependencies]
rand_core = { path = "rand_core", version = "0.4" }
rand_hc = { path = "rand_hc", version = "0.1" }
rand_chacha = { path = "rand_chacha", version = "0.2" }
rand_pcg = { path = "rand_pcg", version = "0.1", optional = true }
# Do not depend on 'getrandom_package' directly; use the 'getrandom' feature!
getrandom_package = { version = "0.1.1", package = "getrandom", optional = true }
Expand All @@ -75,9 +75,9 @@ libc = { version = "0.2.22", default-features = false }
[dev-dependencies]
rand_pcg = { path = "rand_pcg", version = "0.1" }
# Only for benches:
rand_hc = { path = "rand_hc", version = "0.1" }
rand_xoshiro = { path = "rand_xoshiro", version = "0.2" }
rand_isaac = { path = "rand_isaac", version = "0.1" }
rand_chacha = { path = "rand_chacha", version = "0.2" }
rand_xorshift = { path = "rand_xorshift", version = "0.1" }
rand_distr = { path = "rand_distr", version = "0.1" }

Expand Down
51 changes: 20 additions & 31 deletions benches/generators.rs
Expand Up @@ -7,6 +7,7 @@
// except according to those terms.

#![feature(test)]
#![allow(non_snake_case)]

extern crate test;
extern crate rand;
Expand All @@ -27,8 +28,8 @@ use rand::prelude::*;
use rand::rngs::adapter::ReseedingRng;
use rand::rngs::OsRng;
use rand_isaac::{IsaacRng, Isaac64Rng};
use rand_chacha::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng};
use rand_hc::{Hc128Rng, Hc128Core};
use rand_chacha::{ChaCha20Core, ChaCha8Rng, ChaCha12Rng, ChaCha20Rng};
use rand_hc::{Hc128Rng};
use rand_pcg::{Lcg64Xsh32, Mcg128Xsl64};
use rand_xorshift::XorShiftRng;
use rand_xoshiro::{Xoshiro256StarStar, Xoshiro256Plus, Xoshiro128StarStar,
Expand Down Expand Up @@ -165,46 +166,34 @@ init_gen!(init_isaac, IsaacRng);
init_gen!(init_isaac64, Isaac64Rng);
init_gen!(init_chacha, ChaCha20Rng);

const RESEEDING_BYTES_LEN: usize = 1024 * 1024;
const RESEEDING_BENCH_N: u64 = 16;

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(Hc128Core::from_entropy(),
RESEEDING_THRESHOLD,
OsRng);
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) => {
macro_rules! reseeding_bytes {
($fnn:ident, $thresh:expr) => {
#[bench]
fn $fnn(b: &mut Bencher) {
let mut rng = ReseedingRng::new(Hc128Core::from_entropy(),
RESEEDING_THRESHOLD,
let mut rng = ReseedingRng::new(ChaCha20Core::from_entropy(),
$thresh * 1024,
OsRng);
let mut buf = [0u8; RESEEDING_BYTES_LEN];
b.iter(|| {
let mut accum: $ty = 0;
for _ in 0..RAND_BENCH_N {
accum = accum.wrapping_add(rng.gen::<$ty>());
for _ in 0..RESEEDING_BENCH_N {
rng.fill_bytes(&mut buf);
black_box(&buf);
}
accum
});
b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N;
b.bytes = RESEEDING_BYTES_LEN as u64 * RESEEDING_BENCH_N;
}
}
}

reseeding_uint!(reseeding_hc128_u32, u32);
reseeding_uint!(reseeding_hc128_u64, u64);
reseeding_bytes!(reseeding_chacha20_4k, 4);
reseeding_bytes!(reseeding_chacha20_16k, 16);
reseeding_bytes!(reseeding_chacha20_32k, 32);
reseeding_bytes!(reseeding_chacha20_64k, 64);
reseeding_bytes!(reseeding_chacha20_256k, 256);
reseeding_bytes!(reseeding_chacha20_1M, 1024);


macro_rules! threadrng_uint {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Expand Up @@ -62,7 +62,7 @@
extern crate getrandom_package as getrandom;

extern crate rand_core;
extern crate rand_hc;
extern crate rand_chacha;
#[cfg(feature="small_rng")] extern crate rand_pcg;

#[cfg(feature = "log")] #[macro_use] extern crate log;
Expand Down
31 changes: 16 additions & 15 deletions src/rngs/adapter/reseeding.rs
Expand Up @@ -24,7 +24,7 @@ use rand_core::block::{BlockRngCore, BlockRng};
/// - After `clone()`, the clone will be reseeded on first use.
/// - After a process is forked, the RNG in the child process is reseeded within
/// the next few generated values, depending on the block size of the
/// underlying PRNG. For [`ChaCha20Rng`] and [`Hc128Core`] this is a maximum of
/// underlying PRNG. For ChaCha and Hc128 this is a maximum of
/// 15 `u32` values before reseeding.
/// - After the PRNG has generated a configurable number of random bytes.
///
Expand Down Expand Up @@ -61,12 +61,12 @@ use rand_core::block::{BlockRngCore, BlockRng};
/// # extern crate rand_chacha;
/// # fn main() {
/// use rand::prelude::*;
/// use rand_chacha::ChaChaCore; // Internal part of ChaChaRng that
/// use rand_chacha::ChaCha20Core; // Internal part of ChaChaRng that
/// // implements BlockRngCore
/// use rand::rngs::OsRng;
/// use rand::rngs::adapter::ReseedingRng;
///
/// let prng = ChaChaCore::from_entropy();
/// let prng = ChaCha20Core::from_entropy();
/// let mut reseeding_rng = ReseedingRng::new(prng, 0, OsRng);
///
/// println!("{}", reseeding_rng.gen::<u64>());
Expand All @@ -77,7 +77,6 @@ use rand_core::block::{BlockRngCore, BlockRng};
/// ```
///
/// [`ChaCha20Core`]: ../../../rand_chacha/struct.ChaCha20Core.html
/// [`Hc128Core`]: rand_hc::Hc128Core
/// [`BlockRngCore`]: rand_core::block::BlockRngCore
/// [`ReseedingRng::new`]: ReseedingRng::new
/// [`reseed()`]: ReseedingRng::reseed
Expand Down Expand Up @@ -325,32 +324,34 @@ mod fork {
#[cfg(test)]
mod test {
use {Rng, SeedableRng};
use rand_hc::Hc128Core;
use rand_chacha::ChaCha8Core;
use rngs::mock::StepRng;
use super::ReseedingRng;

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

// Currently we only support for arrays up to length 32.
// TODO: cannot generate seq via Rng::gen because it uses different alg
let mut buf = [0u32; 32]; // Needs to be a multiple of the RNGs result
// size to test exactly.
reseeding.fill(&mut buf);
let rng = ChaCha8Core::from_rng(&mut zero).unwrap();
let thresh = 1; // reseed every time the buffer is exhausted
let mut reseeding = ReseedingRng::new(rng, thresh, zero);

// RNG buffer size is [u32; 64]
// Debug is only implemented up to length 32 so use two arrays
let mut buf = ([0u32; 32], [0u32; 32]);
reseeding.fill(&mut buf.0);
reseeding.fill(&mut buf.1);
let seq = buf;
for _ in 0..10 {
reseeding.fill(&mut buf);
reseeding.fill(&mut buf.0);
reseeding.fill(&mut buf.1);
assert_eq!(buf, seq);
}
}

#[test]
fn test_clone_reseeding() {
let mut zero = StepRng::new(0, 0);
let rng = Hc128Core::from_rng(&mut zero).unwrap();
let rng = ChaCha8Core::from_rng(&mut zero).unwrap();
let mut rng1 = ReseedingRng::new(rng, 32*4, zero);

let first: u32 = rng1.gen();
Expand Down
1 change: 1 addition & 0 deletions src/rngs/small.rs
Expand Up @@ -73,6 +73,7 @@ type Rng = ::rand_pcg::Pcg32;
///
/// [`StdRng`]: crate::rngs::StdRng
/// [`thread_rng`]: crate::thread_rng
/// [rand_chacha]: https://crates.io/crates/rand_chacha
/// [rand_pcg]: https://crates.io/crates/rand_pcg
#[derive(Clone, Debug)]
pub struct SmallRng(Rng);
Expand Down
47 changes: 27 additions & 20 deletions src/rngs/std.rs
Expand Up @@ -9,28 +9,25 @@
//! The standard RNG

use {RngCore, CryptoRng, Error, SeedableRng};
use rand_hc::Hc128Rng;
use rand_chacha::ChaCha20Rng;

/// The standard RNG. The PRNG algorithm in `StdRng` is chosen to be efficient
/// on the current platform, to be statistically strong and unpredictable
/// (meaning a cryptographically secure PRNG).
///
/// The current algorithm used on all platforms is [HC-128], found in the
/// [rand_hc] crate.
/// The current algorithm used is the ChaCha block cipher with either 20 or 12
/// rounds (see the `stdrng_*` feature flags, documented in the README).
/// This may change as new evidence of cipher security and performance
/// becomes available.
///
/// Reproducibility of output from this generator is however not required, thus
/// future library versions may use a different internal generator with
/// different output. Further, this generator may not be portable and can
/// produce different output depending on the architecture. If you require
/// reproducible output, use a named RNG, for example [`ChaCha20Rng`] from the
/// [rand_chacha] crate.
/// The algorithm is deterministic but should not be considered reproducible
/// due to dependence on configuration and possible replacement in future
/// library versions. For a secure reproducible generator, we recommend use of
/// the [rand_chacha] crate directly.
///
/// [HC-128]: rand_hc::Hc128Rng
/// [`ChaCha20Rng`]: ../../rand_chacha/struct.ChaCha20Rng.html
/// [rand_hc]: https://crates.io/crates/rand_hc
/// [rand_chacha]: https://crates.io/crates/rand_chacha
#[derive(Clone, Debug)]
pub struct StdRng(Hc128Rng);
pub struct StdRng(ChaCha20Rng);

impl RngCore for StdRng {
#[inline(always)]
Expand All @@ -53,14 +50,14 @@ impl RngCore for StdRng {
}

impl SeedableRng for StdRng {
type Seed = <Hc128Rng as SeedableRng>::Seed;
type Seed = <ChaCha20Rng as SeedableRng>::Seed;

fn from_seed(seed: Self::Seed) -> Self {
StdRng(Hc128Rng::from_seed(seed))
StdRng(ChaCha20Rng::from_seed(seed))
}

fn from_rng<R: RngCore>(rng: R) -> Result<Self, Error> {
Hc128Rng::from_rng(rng).map(StdRng)
ChaCha20Rng::from_rng(rng).map(StdRng)
}
}

Expand All @@ -74,12 +71,22 @@ mod test {

#[test]
fn test_stdrng_construction() {
// Test value-stability of StdRng. This is expected to break any time
// the algorithm is changed.
let seed = [1,0,0,0, 23,0,0,0, 200,1,0,0, 210,30,0,0,
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0];
let mut rng1 = StdRng::from_seed(seed);
assert_eq!(rng1.next_u64(), 15759097995037006553);

let mut rng2 = StdRng::from_rng(rng1).unwrap();
assert_eq!(rng2.next_u64(), 6766915756997287454);
#[cfg(any(feature="stdrng_strong", not(feature="stdrng_fast")))]
let target = [3950704604716924505, 5573172343717151650];
#[cfg(all(not(feature="stdrng_strong"), feature="stdrng_fast"))]
let target = [10719222850664546238, 14064965282130556830];

let mut rng0 = StdRng::from_seed(seed);
let x0 = rng0.next_u64();

let mut rng1 = StdRng::from_rng(rng0).unwrap();
let x1 = rng1.next_u64();

assert_eq!([x0, x1], target);
}
}
46 changes: 17 additions & 29 deletions src/rngs/thread.rs
Expand Up @@ -13,7 +13,7 @@ use std::cell::UnsafeCell;
use {RngCore, CryptoRng, SeedableRng, Error};
use rngs::adapter::ReseedingRng;
use rngs::OsRng;
use rand_hc::Hc128Core;
use rand_chacha::ChaCha20Core;

// Rationale for using `UnsafeCell` in `ThreadRng`:
//
Expand All @@ -33,49 +33,37 @@ use rand_hc::Hc128Core;
// it ensures `ThreadRng` stays `!Send` and `!Sync`, and implements `Clone`.


// Number of generated bytes after which to reseed `TreadRng`.
//
// The time it takes to reseed HC-128 is roughly equivalent to generating 7 KiB.
// We pick a treshold here that is large enough to not reduce the average
// performance too much, but also small enough to not make reseeding something
// that basically never happens.
const THREAD_RNG_RESEED_THRESHOLD: u64 = 32*1024*1024; // 32 MiB
// Number of generated bytes after which to reseed `ThreadRng`.
// According to benchmarks, reseeding has a noticable impact with thresholds
// of 32 kB and less. We choose 64 kB to avoid significant overhead.
const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64;

/// The type returned by [`thread_rng`], essentially just a reference to the
/// PRNG in thread-local memory.
///
/// `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 [`OsRng`]
/// as required.
///
/// Note that the reseeding is done as an extra precaution against entropy
/// leaks and is in theory unnecessary — to predict `ThreadRng`'s output, an
/// attacker would have to either determine most of the RNG's seed or internal
/// state, or crack the algorithm used.
/// `ThreadRng` uses the same PRNG as [`StdRng`] for security and performance.
/// As hinted by the name, the generator is thread-local. `ThreadRng` is a
/// handle to this generator and thus supports `Copy`, but not `Send` or `Sync`.
///
/// Like [`StdRng`], `ThreadRng` is a cryptographically secure PRNG. The current
/// algorithm used is [HC-128], which is an array-based PRNG that trades memory
/// usage for better performance. This makes it similar to ISAAC, the algorithm
/// used in `ThreadRng` before rand 0.5.
/// Unlike `StdRng`, `ThreadRng` uses the [`ReseedingRng`] wrapper to reseed
/// the PRNG from fresh entropy every 64 kiB of random data.
/// [`OsRng`] is used to provide seed data.
///
/// Cloning this handle just produces a new reference to the same thread-local
/// generator.
/// Note that the reseeding is done as an extra precaution against side-channel
/// attacks and mis-use (e.g. if somehow weak entropy were supplied initially).
/// The PRNG algorithms used are assumed to be secure.
///
/// [`ReseedingRng`]: crate::rngs::adapter::ReseedingRng
/// [`StdRng`]: crate::rngs::StdRng
/// [HC-128]: rand_hc::Hc128Rng
#[derive(Copy, Clone, Debug)]
pub struct ThreadRng {
// use of raw pointer implies type is neither Send nor Sync
rng: *mut ReseedingRng<Hc128Core, OsRng>,
rng: *mut ReseedingRng<ChaCha20Core, OsRng>,
}

thread_local!(
static THREAD_RNG_KEY: UnsafeCell<ReseedingRng<Hc128Core, OsRng>> = {
let r = Hc128Core::from_rng(OsRng).unwrap_or_else(|err|
static THREAD_RNG_KEY: UnsafeCell<ReseedingRng<ChaCha20Core, OsRng>> = {
let r = ChaCha20Core::from_rng(OsRng).unwrap_or_else(|err|
panic!("could not initialize thread_rng: {}", err));
let rng = ReseedingRng::new(r,
THREAD_RNG_RESEED_THRESHOLD,
Expand Down

0 comments on commit 33bd404

Please sign in to comment.