Skip to content

Commit 33bd404

Browse files
committed
Replace StdRng algorithm with ChaCha20 and adjust reseeding threshold
1 parent 41b9743 commit 33bd404

File tree

7 files changed

+84
-98
lines changed

7 files changed

+84
-98
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ members = [
5555

5656
[dependencies]
5757
rand_core = { path = "rand_core", version = "0.4" }
58-
rand_hc = { path = "rand_hc", version = "0.1" }
58+
rand_chacha = { path = "rand_chacha", version = "0.2" }
5959
rand_pcg = { path = "rand_pcg", version = "0.1", optional = true }
6060
# Do not depend on 'getrandom_package' directly; use the 'getrandom' feature!
6161
getrandom_package = { version = "0.1.1", package = "getrandom", optional = true }
@@ -75,9 +75,9 @@ libc = { version = "0.2.22", default-features = false }
7575
[dev-dependencies]
7676
rand_pcg = { path = "rand_pcg", version = "0.1" }
7777
# Only for benches:
78+
rand_hc = { path = "rand_hc", version = "0.1" }
7879
rand_xoshiro = { path = "rand_xoshiro", version = "0.2" }
7980
rand_isaac = { path = "rand_isaac", version = "0.1" }
80-
rand_chacha = { path = "rand_chacha", version = "0.2" }
8181
rand_xorshift = { path = "rand_xorshift", version = "0.1" }
8282
rand_distr = { path = "rand_distr", version = "0.1" }
8383

benches/generators.rs

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// except according to those terms.
88

99
#![feature(test)]
10+
#![allow(non_snake_case)]
1011

1112
extern crate test;
1213
extern crate rand;
@@ -27,8 +28,8 @@ use rand::prelude::*;
2728
use rand::rngs::adapter::ReseedingRng;
2829
use rand::rngs::OsRng;
2930
use rand_isaac::{IsaacRng, Isaac64Rng};
30-
use rand_chacha::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng};
31-
use rand_hc::{Hc128Rng, Hc128Core};
31+
use rand_chacha::{ChaCha20Core, ChaCha8Rng, ChaCha12Rng, ChaCha20Rng};
32+
use rand_hc::{Hc128Rng};
3233
use rand_pcg::{Lcg64Xsh32, Mcg128Xsl64};
3334
use rand_xorshift::XorShiftRng;
3435
use rand_xoshiro::{Xoshiro256StarStar, Xoshiro256Plus, Xoshiro128StarStar,
@@ -165,46 +166,34 @@ init_gen!(init_isaac, IsaacRng);
165166
init_gen!(init_isaac64, Isaac64Rng);
166167
init_gen!(init_chacha, ChaCha20Rng);
167168

169+
const RESEEDING_BYTES_LEN: usize = 1024 * 1024;
170+
const RESEEDING_BENCH_N: u64 = 16;
168171

169-
const RESEEDING_THRESHOLD: u64 = 1024*1024*1024; // something high enough to get
170-
// deterministic measurements
171-
172-
#[bench]
173-
fn reseeding_hc128_bytes(b: &mut Bencher) {
174-
let mut rng = ReseedingRng::new(Hc128Core::from_entropy(),
175-
RESEEDING_THRESHOLD,
176-
OsRng);
177-
let mut buf = [0u8; BYTES_LEN];
178-
b.iter(|| {
179-
for _ in 0..RAND_BENCH_N {
180-
rng.fill_bytes(&mut buf);
181-
black_box(buf);
182-
}
183-
});
184-
b.bytes = BYTES_LEN as u64 * RAND_BENCH_N;
185-
}
186-
187-
macro_rules! reseeding_uint {
188-
($fnn:ident, $ty:ty) => {
172+
macro_rules! reseeding_bytes {
173+
($fnn:ident, $thresh:expr) => {
189174
#[bench]
190175
fn $fnn(b: &mut Bencher) {
191-
let mut rng = ReseedingRng::new(Hc128Core::from_entropy(),
192-
RESEEDING_THRESHOLD,
176+
let mut rng = ReseedingRng::new(ChaCha20Core::from_entropy(),
177+
$thresh * 1024,
193178
OsRng);
179+
let mut buf = [0u8; RESEEDING_BYTES_LEN];
194180
b.iter(|| {
195-
let mut accum: $ty = 0;
196-
for _ in 0..RAND_BENCH_N {
197-
accum = accum.wrapping_add(rng.gen::<$ty>());
181+
for _ in 0..RESEEDING_BENCH_N {
182+
rng.fill_bytes(&mut buf);
183+
black_box(&buf);
198184
}
199-
accum
200185
});
201-
b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N;
186+
b.bytes = RESEEDING_BYTES_LEN as u64 * RESEEDING_BENCH_N;
202187
}
203188
}
204189
}
205190

206-
reseeding_uint!(reseeding_hc128_u32, u32);
207-
reseeding_uint!(reseeding_hc128_u64, u64);
191+
reseeding_bytes!(reseeding_chacha20_4k, 4);
192+
reseeding_bytes!(reseeding_chacha20_16k, 16);
193+
reseeding_bytes!(reseeding_chacha20_32k, 32);
194+
reseeding_bytes!(reseeding_chacha20_64k, 64);
195+
reseeding_bytes!(reseeding_chacha20_256k, 256);
196+
reseeding_bytes!(reseeding_chacha20_1M, 1024);
208197

209198

210199
macro_rules! threadrng_uint {

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
extern crate getrandom_package as getrandom;
6363

6464
extern crate rand_core;
65-
extern crate rand_hc;
65+
extern crate rand_chacha;
6666
#[cfg(feature="small_rng")] extern crate rand_pcg;
6767

6868
#[cfg(feature = "log")] #[macro_use] extern crate log;

src/rngs/adapter/reseeding.rs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use rand_core::block::{BlockRngCore, BlockRng};
2424
/// - After `clone()`, the clone will be reseeded on first use.
2525
/// - After a process is forked, the RNG in the child process is reseeded within
2626
/// the next few generated values, depending on the block size of the
27-
/// underlying PRNG. For [`ChaCha20Rng`] and [`Hc128Core`] this is a maximum of
27+
/// underlying PRNG. For ChaCha and Hc128 this is a maximum of
2828
/// 15 `u32` values before reseeding.
2929
/// - After the PRNG has generated a configurable number of random bytes.
3030
///
@@ -61,12 +61,12 @@ use rand_core::block::{BlockRngCore, BlockRng};
6161
/// # extern crate rand_chacha;
6262
/// # fn main() {
6363
/// use rand::prelude::*;
64-
/// use rand_chacha::ChaChaCore; // Internal part of ChaChaRng that
64+
/// use rand_chacha::ChaCha20Core; // Internal part of ChaChaRng that
6565
/// // implements BlockRngCore
6666
/// use rand::rngs::OsRng;
6767
/// use rand::rngs::adapter::ReseedingRng;
6868
///
69-
/// let prng = ChaChaCore::from_entropy();
69+
/// let prng = ChaCha20Core::from_entropy();
7070
/// let mut reseeding_rng = ReseedingRng::new(prng, 0, OsRng);
7171
///
7272
/// println!("{}", reseeding_rng.gen::<u64>());
@@ -77,7 +77,6 @@ use rand_core::block::{BlockRngCore, BlockRng};
7777
/// ```
7878
///
7979
/// [`ChaCha20Core`]: ../../../rand_chacha/struct.ChaCha20Core.html
80-
/// [`Hc128Core`]: rand_hc::Hc128Core
8180
/// [`BlockRngCore`]: rand_core::block::BlockRngCore
8281
/// [`ReseedingRng::new`]: ReseedingRng::new
8382
/// [`reseed()`]: ReseedingRng::reseed
@@ -325,32 +324,34 @@ mod fork {
325324
#[cfg(test)]
326325
mod test {
327326
use {Rng, SeedableRng};
328-
use rand_hc::Hc128Core;
327+
use rand_chacha::ChaCha8Core;
329328
use rngs::mock::StepRng;
330329
use super::ReseedingRng;
331330

332331
#[test]
333332
fn test_reseeding() {
334333
let mut zero = StepRng::new(0, 0);
335-
let rng = Hc128Core::from_rng(&mut zero).unwrap();
336-
let mut reseeding = ReseedingRng::new(rng, 32*4, zero);
337-
338-
// Currently we only support for arrays up to length 32.
339-
// TODO: cannot generate seq via Rng::gen because it uses different alg
340-
let mut buf = [0u32; 32]; // Needs to be a multiple of the RNGs result
341-
// size to test exactly.
342-
reseeding.fill(&mut buf);
334+
let rng = ChaCha8Core::from_rng(&mut zero).unwrap();
335+
let thresh = 1; // reseed every time the buffer is exhausted
336+
let mut reseeding = ReseedingRng::new(rng, thresh, zero);
337+
338+
// RNG buffer size is [u32; 64]
339+
// Debug is only implemented up to length 32 so use two arrays
340+
let mut buf = ([0u32; 32], [0u32; 32]);
341+
reseeding.fill(&mut buf.0);
342+
reseeding.fill(&mut buf.1);
343343
let seq = buf;
344344
for _ in 0..10 {
345-
reseeding.fill(&mut buf);
345+
reseeding.fill(&mut buf.0);
346+
reseeding.fill(&mut buf.1);
346347
assert_eq!(buf, seq);
347348
}
348349
}
349350

350351
#[test]
351352
fn test_clone_reseeding() {
352353
let mut zero = StepRng::new(0, 0);
353-
let rng = Hc128Core::from_rng(&mut zero).unwrap();
354+
let rng = ChaCha8Core::from_rng(&mut zero).unwrap();
354355
let mut rng1 = ReseedingRng::new(rng, 32*4, zero);
355356

356357
let first: u32 = rng1.gen();

src/rngs/small.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type Rng = ::rand_pcg::Pcg32;
7373
///
7474
/// [`StdRng`]: crate::rngs::StdRng
7575
/// [`thread_rng`]: crate::thread_rng
76+
/// [rand_chacha]: https://crates.io/crates/rand_chacha
7677
/// [rand_pcg]: https://crates.io/crates/rand_pcg
7778
#[derive(Clone, Debug)]
7879
pub struct SmallRng(Rng);

src/rngs/std.rs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,25 @@
99
//! The standard RNG
1010
1111
use {RngCore, CryptoRng, Error, SeedableRng};
12-
use rand_hc::Hc128Rng;
12+
use rand_chacha::ChaCha20Rng;
1313

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

3532
impl RngCore for StdRng {
3633
#[inline(always)]
@@ -53,14 +50,14 @@ impl RngCore for StdRng {
5350
}
5451

5552
impl SeedableRng for StdRng {
56-
type Seed = <Hc128Rng as SeedableRng>::Seed;
53+
type Seed = <ChaCha20Rng as SeedableRng>::Seed;
5754

5855
fn from_seed(seed: Self::Seed) -> Self {
59-
StdRng(Hc128Rng::from_seed(seed))
56+
StdRng(ChaCha20Rng::from_seed(seed))
6057
}
6158

6259
fn from_rng<R: RngCore>(rng: R) -> Result<Self, Error> {
63-
Hc128Rng::from_rng(rng).map(StdRng)
60+
ChaCha20Rng::from_rng(rng).map(StdRng)
6461
}
6562
}
6663

@@ -74,12 +71,22 @@ mod test {
7471

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

82-
let mut rng2 = StdRng::from_rng(rng1).unwrap();
83-
assert_eq!(rng2.next_u64(), 6766915756997287454);
79+
#[cfg(any(feature="stdrng_strong", not(feature="stdrng_fast")))]
80+
let target = [3950704604716924505, 5573172343717151650];
81+
#[cfg(all(not(feature="stdrng_strong"), feature="stdrng_fast"))]
82+
let target = [10719222850664546238, 14064965282130556830];
83+
84+
let mut rng0 = StdRng::from_seed(seed);
85+
let x0 = rng0.next_u64();
86+
87+
let mut rng1 = StdRng::from_rng(rng0).unwrap();
88+
let x1 = rng1.next_u64();
89+
90+
assert_eq!([x0, x1], target);
8491
}
8592
}

src/rngs/thread.rs

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::cell::UnsafeCell;
1313
use {RngCore, CryptoRng, SeedableRng, Error};
1414
use rngs::adapter::ReseedingRng;
1515
use rngs::OsRng;
16-
use rand_hc::Hc128Core;
16+
use rand_chacha::ChaCha20Core;
1717

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

3535

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

4441
/// The type returned by [`thread_rng`], essentially just a reference to the
4542
/// PRNG in thread-local memory.
4643
///
47-
/// `ThreadRng` uses [`ReseedingRng`] wrapping the same PRNG as [`StdRng`],
48-
/// which is reseeded after generating 32 MiB of random data. A single instance
49-
/// is cached per thread and the returned `ThreadRng` is a reference to this
50-
/// instance — hence `ThreadRng` is neither `Send` nor `Sync` but is safe to use
51-
/// within a single thread. This RNG is seeded and reseeded via [`OsRng`]
52-
/// as required.
53-
///
54-
/// Note that the reseeding is done as an extra precaution against entropy
55-
/// leaks and is in theory unnecessary — to predict `ThreadRng`'s output, an
56-
/// attacker would have to either determine most of the RNG's seed or internal
57-
/// state, or crack the algorithm used.
44+
/// `ThreadRng` uses the same PRNG as [`StdRng`] for security and performance.
45+
/// As hinted by the name, the generator is thread-local. `ThreadRng` is a
46+
/// handle to this generator and thus supports `Copy`, but not `Send` or `Sync`.
5847
///
59-
/// Like [`StdRng`], `ThreadRng` is a cryptographically secure PRNG. The current
60-
/// algorithm used is [HC-128], which is an array-based PRNG that trades memory
61-
/// usage for better performance. This makes it similar to ISAAC, the algorithm
62-
/// used in `ThreadRng` before rand 0.5.
48+
/// Unlike `StdRng`, `ThreadRng` uses the [`ReseedingRng`] wrapper to reseed
49+
/// the PRNG from fresh entropy every 64 kiB of random data.
50+
/// [`OsRng`] is used to provide seed data.
6351
///
64-
/// Cloning this handle just produces a new reference to the same thread-local
65-
/// generator.
52+
/// Note that the reseeding is done as an extra precaution against side-channel
53+
/// attacks and mis-use (e.g. if somehow weak entropy were supplied initially).
54+
/// The PRNG algorithms used are assumed to be secure.
6655
///
6756
/// [`ReseedingRng`]: crate::rngs::adapter::ReseedingRng
6857
/// [`StdRng`]: crate::rngs::StdRng
69-
/// [HC-128]: rand_hc::Hc128Rng
7058
#[derive(Copy, Clone, Debug)]
7159
pub struct ThreadRng {
7260
// use of raw pointer implies type is neither Send nor Sync
73-
rng: *mut ReseedingRng<Hc128Core, OsRng>,
61+
rng: *mut ReseedingRng<ChaCha20Core, OsRng>,
7462
}
7563

7664
thread_local!(
77-
static THREAD_RNG_KEY: UnsafeCell<ReseedingRng<Hc128Core, OsRng>> = {
78-
let r = Hc128Core::from_rng(OsRng).unwrap_or_else(|err|
65+
static THREAD_RNG_KEY: UnsafeCell<ReseedingRng<ChaCha20Core, OsRng>> = {
66+
let r = ChaCha20Core::from_rng(OsRng).unwrap_or_else(|err|
7967
panic!("could not initialize thread_rng: {}", err));
8068
let rng = ReseedingRng::new(r,
8169
THREAD_RNG_RESEED_THRESHOLD,

0 commit comments

Comments
 (0)