From 33bd4040f632ddbf5f80acd17ae31f07304bb914 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 13 May 2019 11:20:06 +0100 Subject: [PATCH] Replace StdRng algorithm with ChaCha20 and adjust reseeding threshold --- Cargo.toml | 4 +-- benches/generators.rs | 51 ++++++++++++++--------------------- src/lib.rs | 2 +- src/rngs/adapter/reseeding.rs | 31 ++++++++++----------- src/rngs/small.rs | 1 + src/rngs/std.rs | 47 ++++++++++++++++++-------------- src/rngs/thread.rs | 46 ++++++++++++------------------- 7 files changed, 84 insertions(+), 98 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 74a82ea7855..b7a3d46b93e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } @@ -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" } diff --git a/benches/generators.rs b/benches/generators.rs index 1c609983a3e..2def704b388 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -7,6 +7,7 @@ // except according to those terms. #![feature(test)] +#![allow(non_snake_case)] extern crate test; extern crate rand; @@ -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, @@ -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 { diff --git a/src/lib.rs b/src/lib.rs index 7cd38a9b65c..6798206e1d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index 30e8288ff0e..7831b90c433 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -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. /// @@ -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::()); @@ -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 @@ -325,24 +324,26 @@ 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); } } @@ -350,7 +351,7 @@ mod test { #[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(); diff --git a/src/rngs/small.rs b/src/rngs/small.rs index 1da4084f2ab..6a96338e802 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -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); diff --git a/src/rngs/std.rs b/src/rngs/std.rs index 02089db54bd..982a55fb2f2 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -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)] @@ -53,14 +50,14 @@ impl RngCore for StdRng { } impl SeedableRng for StdRng { - type Seed = ::Seed; + type Seed = ::Seed; fn from_seed(seed: Self::Seed) -> Self { - StdRng(Hc128Rng::from_seed(seed)) + StdRng(ChaCha20Rng::from_seed(seed)) } fn from_rng(rng: R) -> Result { - Hc128Rng::from_rng(rng).map(StdRng) + ChaCha20Rng::from_rng(rng).map(StdRng) } } @@ -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); } } diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index f2312e4cbbe..88807fb3dbd 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -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`: // @@ -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, + rng: *mut ReseedingRng, } thread_local!( - static THREAD_RNG_KEY: UnsafeCell> = { - let r = Hc128Core::from_rng(OsRng).unwrap_or_else(|err| + static THREAD_RNG_KEY: UnsafeCell> = { + 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,