Skip to content

Commit

Permalink
Add stdrng_* features and change RNG and reseeding threshold
Browse files Browse the repository at this point in the history
  • Loading branch information
dhardy committed May 7, 2019
1 parent cf527e9 commit e32a474
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 86 deletions.
8 changes: 6 additions & 2 deletions Cargo.toml
Expand Up @@ -34,6 +34,10 @@ stdweb = ["getrandom/stdweb"]

# Configuration:
simd_support = ["packed_simd"] # enables SIMD support
# RNG config; lower-priority alternatives are implied to avoid exponential
# number of configurations
stdrng_fast = []
stdrng_strong = ["stdrng_fast"]

[workspace]
members = [
Expand All @@ -53,7 +57,7 @@ members = [
[dependencies]
rand_core = { path = "rand_core", version = "0.4" }
rand_pcg = { path = "rand_pcg", version = "0.1" }
rand_hc = { path = "rand_hc", version = "0.1" }
rand_chacha = { path = "rand_chacha", version = "0.2" }
getrandom = { version = "0.1.1", optional = true }
log = { version = "0.4", optional = true }

Expand All @@ -72,9 +76,9 @@ libc = { version = "0.2.22", default-features = false }
# This has a histogram implementation used for testing uniformity.
average = "0.9.2"
# Only for benches:
rand_hc = { path = "rand_hc", version = "0.1" }
rand_xoshiro = { path = "rand_xoshiro", version = "0.1" }
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
9 changes: 9 additions & 0 deletions README.md
Expand Up @@ -106,6 +106,15 @@ Additionally, these features configure Rand:
- `simd_support` (experimental) enables sampling of SIMD values
(uniformly random SIMD integers and floats)

`StdRng` and `ThreadRng` can be configured via the following feature flags.
Note that in case multiple options are enabled simultaneously, the strongest is
used. If none are enabled the strongest option is used.

- `stdrng_strong` selects an algorithm for `StdRng` and `ThreadRng` with a
conservative security margin (currently ChaCha20).
- `stdrng_fast` selects an algorithm for `StdRng` and `ThreadRng` for
performance and good expectations of security (currently ChaCha12).

Rand supports limited functionality in `no_std` mode (enabled via
`default-features = false`). In this case, `OsRng` and `from_entropy` are
unavailable (unless `getrandom` is enabled), large parts of `seq` are
Expand Down
50 changes: 19 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::*;
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,33 @@ 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(ChaCha12Core::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_chacha12_4k, 4);
reseeding_bytes!(reseeding_chacha12_16k, 16);
reseeding_bytes!(reseeding_chacha12_64k, 64);
reseeding_bytes!(reseeding_chacha12_256k, 256);
reseeding_bytes!(reseeding_chacha12_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;

extern crate rand_core;
extern crate rand_hc;
extern crate rand_chacha;
extern crate rand_pcg;

#[cfg(feature = "log")] #[macro_use] extern crate log;
Expand Down
13 changes: 6 additions & 7 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 @@ -333,14 +332,14 @@ 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 rng = ChaCha8Core::from_rng(&mut zero).unwrap();
let mut reseeding = ReseedingRng::new(rng, 32*4, zero);

// Currently we only support for arrays up to length 32.
Expand All @@ -358,7 +357,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();
Expand Down
38 changes: 22 additions & 16 deletions src/rngs/std.rs
Expand Up @@ -9,28 +9,34 @@
//! The standard RNG

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

#[cfg(any(feature="stdrng_strong", not(feature="stdrng_fast")))] // strong or unspecified
type Rng = rand_chacha::ChaCha20Rng;
#[cfg(all(not(feature="stdrng_strong"), feature="stdrng_fast"))] // not strong and fast
type Rng = rand_chacha::ChaCha12Rng;

#[cfg(any(feature="stdrng_strong", not(feature="stdrng_fast")))] // strong or unspecified
pub(crate) type Core = rand_chacha::ChaCha20Core;
#[cfg(all(not(feature="stdrng_strong"), feature="stdrng_fast"))] // not strong and fast
pub(crate) type Core = rand_chacha::ChaCha12Core;

/// 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(Rng);

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

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

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

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

Expand Down
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 rngs::std::Core;

// 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 16 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<Core, 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<Core, OsRng>> = {
let r = Core::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 e32a474

Please sign in to comment.