Skip to content

Commit 45717eb

Browse files
authored
Merge pull request #252 from pitdicker/port_reseeding
Port reseeding
2 parents ad7c569 + cc85aa6 commit 45717eb

File tree

3 files changed

+174
-37
lines changed

3 files changed

+174
-37
lines changed

Diff for: benches/generators.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ const BYTES_LEN: usize = 1024;
99
use std::mem::size_of;
1010
use test::{black_box, Bencher};
1111

12-
use rand::{Rng, NewRng, StdRng, OsRng, JitterRng};
12+
use rand::{Rng, NewRng, StdRng, OsRng, JitterRng, EntropyRng};
1313
use rand::{XorShiftRng, Hc128Rng, IsaacRng, Isaac64Rng, ChaChaRng};
14+
use rand::reseeding::ReseedingRng;
1415

1516
macro_rules! gen_bytes {
1617
($fnn:ident, $gen:ident) => {
@@ -162,3 +163,39 @@ macro_rules! chacha_rounds {
162163
chacha_rounds!(gen_bytes_chacha8, gen_u32_chacha8, gen_u64_chacha8, 8);
163164
chacha_rounds!(gen_bytes_chacha12, gen_u32_chacha12, gen_u64_chacha12, 12);
164165
chacha_rounds!(gen_bytes_chacha20, gen_u32_chacha20, gen_u64_chacha20, 20);
166+
167+
168+
#[bench]
169+
fn reseeding_hc128_bytes(b: &mut Bencher) {
170+
let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(),
171+
128*1024*1024,
172+
EntropyRng::new());
173+
let mut buf = [0u8; BYTES_LEN];
174+
b.iter(|| {
175+
for _ in 0..RAND_BENCH_N {
176+
rng.fill_bytes(&mut buf);
177+
black_box(buf);
178+
}
179+
});
180+
b.bytes = BYTES_LEN as u64 * RAND_BENCH_N;
181+
}
182+
183+
macro_rules! reseeding_uint {
184+
($fnn:ident, $ty:ty) => {
185+
#[bench]
186+
fn $fnn(b: &mut Bencher) {
187+
let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(),
188+
128*1024*1024,
189+
EntropyRng::new());
190+
b.iter(|| {
191+
for _ in 0..RAND_BENCH_N {
192+
black_box(rng.gen::<$ty>());
193+
}
194+
});
195+
b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N;
196+
}
197+
}
198+
}
199+
200+
reseeding_uint!(reseeding_hc128_u32, u32);
201+
reseeding_uint!(reseeding_hc128_u64, u64);

Diff for: src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@
258258
#[cfg(not(feature = "log"))] macro_rules! trace { ($($x:tt)*) => () }
259259
#[cfg(not(feature = "log"))] macro_rules! debug { ($($x:tt)*) => () }
260260
#[cfg(all(feature="std", not(feature = "log")))] macro_rules! info { ($($x:tt)*) => () }
261-
#[cfg(all(feature="std", not(feature = "log")))] macro_rules! warn { ($($x:tt)*) => () }
261+
#[cfg(not(feature = "log"))] macro_rules! warn { ($($x:tt)*) => () }
262262
#[cfg(all(feature="std", not(feature = "log")))] macro_rules! error { ($($x:tt)*) => () }
263263

264264

@@ -1284,7 +1284,7 @@ mod test {
12841284
80, 81, 82, 83, 84, 85, 86, 87];
12851285
for &n in lengths.iter() {
12861286
let mut buffer = [0u8; 87];
1287-
let mut v = &mut buffer[0..n];
1287+
let v = &mut buffer[0..n];
12881288
r.fill_bytes(v);
12891289

12901290
// use this to get nicer error messages.

Diff for: src/reseeding.rs

+134-34
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,58 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
//! A wrapper around another RNG that reseeds it after it
11+
//! A wrapper around another PRNG that reseeds it after it
1212
//! generates a certain number of random bytes.
1313
14-
use {Rng, SeedableRng, Error};
15-
16-
/// A wrapper around any RNG which reseeds the underlying RNG after it
17-
/// has generated a certain number of random bytes.
14+
use {Rng, SeedableRng, Error, ErrorKind};
15+
16+
/// A wrapper around any PRNG which reseeds the underlying PRNG after it has
17+
/// generated a certain number of random bytes.
18+
///
19+
/// Reseeding is never strictly *necessary*. Cryptographic PRNGs don't have a
20+
/// limited number of bytes they can output, or at least not a limit reachable
21+
/// in any practical way. There is no such thing as 'running out of entropy'.
22+
///
23+
/// Some small non-cryptographic PRNGs can have very small periods, for
24+
/// example less than 2<sup>64</sup>. Would reseeding help to ensure that you do
25+
/// not wrap around at the end of the period? A period of 2<sup>64</sup> still
26+
/// takes several centuries of CPU-years on current hardware. Reseeding will
27+
/// actually make things worse, because the reseeded PRNG will just continue
28+
/// somewhere else *in the same period*, with a high chance of overlapping with
29+
/// previously used parts of it.
30+
///
31+
/// # When should you use `ReseedingRng`?
32+
///
33+
/// - Reseeding can be seen as some form of 'security in depth'. Even if in the
34+
/// future a cryptographic weakness is found in the CSPRNG being used,
35+
/// occasionally reseeding should make exploiting it much more difficult or
36+
/// even impossible.
37+
/// - It can be used as a poor man's cryptography (not recommended, just use a
38+
/// good CSPRNG). Previous implementations of `thread_rng` for example used
39+
/// `ReseedingRng` with the ISAAC RNG. That algorithm, although apparently
40+
/// strong and with no known attack, does not come with any proof of security
41+
/// and does not meet the current standards for a cryptographically secure
42+
/// PRNG. By reseeding it frequently (every 32 MiB) it seems safe to assume
43+
/// there is no attack that can operate on the tiny window between reseeds.
44+
///
45+
/// # Error handling
46+
///
47+
/// If reseeding fails, `try_fill_bytes` is the only `Rng` method to report it.
48+
/// For all other `Rng` methods, `ReseedingRng` will not panic but try to
49+
/// handle the error intelligently; if handling the source error fails these
50+
/// methods will continue generating data from the wrapped PRNG without
51+
/// reseeding.
52+
///
53+
/// It is usually best to use the infallible methods `next_u32`, `next_u64` and
54+
/// `fill_bytes` because they can make use of this error handling strategy.
55+
/// Use `try_fill_bytes` and possibly `try_reseed` if you want to handle
56+
/// reseeding errors explicitly.
1857
#[derive(Debug)]
1958
pub struct ReseedingRng<R, Rsdr> {
2059
rng: R,
21-
generation_threshold: u64,
22-
bytes_generated: u64,
23-
/// Controls the behaviour when reseeding the RNG.
2460
reseeder: Rsdr,
61+
threshold: i64,
62+
bytes_until_reseed: i64,
2563
}
2664

2765
impl<R: Rng+SeedableRng, Rsdr: Rng> ReseedingRng<R, Rsdr> {
@@ -30,53 +68,115 @@ impl<R: Rng+SeedableRng, Rsdr: Rng> ReseedingRng<R, Rsdr> {
3068
/// # Arguments
3169
///
3270
/// * `rng`: the random number generator to use.
33-
/// * `generation_threshold`: the number of bytes of entropy at which to reseed the RNG.
34-
/// * `reseeder`: the reseeding object to use.
35-
pub fn new(rng: R, generation_threshold: u64, reseeder: Rsdr) -> ReseedingRng<R,Rsdr> {
71+
/// * `threshold`: the number of generated bytes after which to reseed the RNG.
72+
/// * `reseeder`: the RNG to use for reseeding.
73+
pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> ReseedingRng<R,Rsdr> {
74+
assert!(threshold <= ::core::i64::MAX as u64);
3675
ReseedingRng {
3776
rng: rng,
38-
generation_threshold: generation_threshold,
39-
bytes_generated: 0,
40-
reseeder: reseeder
77+
reseeder: reseeder,
78+
threshold: threshold as i64,
79+
bytes_until_reseed: threshold as i64,
80+
}
81+
}
82+
83+
/// Reseed the internal PRNG.
84+
///
85+
/// This will try to work around errors in the RNG used for reseeding
86+
/// intelligently. If the error kind indicates retrying might help, it will
87+
/// immediately retry a couple of times. If the error kind indicates the
88+
/// seeding RNG is not ready, it will retry later, after `threshold / 256`
89+
/// generated bytes. On other errors in the source RNG, this will skip
90+
/// reseeding and continue using the internal PRNG, until another
91+
/// `threshold` bytes have been generated (at which point it will try
92+
/// reseeding again).
93+
#[inline(never)]
94+
pub fn reseed(&mut self) {
95+
trace!("Reseeding RNG after generating {} bytes",
96+
self.threshold - self.bytes_until_reseed);
97+
self.bytes_until_reseed = self.threshold;
98+
let mut err_count = 0;
99+
loop {
100+
if let Err(e) = R::from_rng(&mut self.reseeder)
101+
.map(|result| self.rng = result) {
102+
let kind = e.kind();
103+
if kind.should_wait() {
104+
self.bytes_until_reseed = self.threshold >> 8;
105+
warn!("Reseeding RNG delayed for {} bytes",
106+
self.bytes_until_reseed);
107+
} else if kind.should_retry() {
108+
err_count += 1;
109+
// Retry immediately for 5 times (arbitrary limit)
110+
if err_count <= 5 { continue; }
111+
}
112+
warn!("Reseeding RNG failed; continuing without reseeding. Error: {}", e);
113+
}
114+
break; // Successfully reseeded, delayed, or given up.
41115
}
42116
}
43117

44118
/// Reseed the internal RNG if the number of bytes that have been
45119
/// generated exceed the threshold.
46-
pub fn reseed_if_necessary(&mut self) {
47-
if self.bytes_generated >= self.generation_threshold {
48-
trace!("Reseeding RNG after {} bytes", self.bytes_generated);
49-
R::from_rng(&mut self.reseeder).map(|result| self.rng = result)
50-
.unwrap_or_else(|err| panic!("reseeding failed: {}", err));
51-
self.bytes_generated = 0;
120+
///
121+
/// If reseeding fails, return an error with the original cause. Note that
122+
/// if the cause has a permanent failure, we report a transient error and
123+
/// skip reseeding; this means that only two error kinds can be reported
124+
/// from this method: `ErrorKind::Transient` and `ErrorKind::NotReady`.
125+
#[inline(never)]
126+
pub fn try_reseed(&mut self) -> Result<(), Error> {
127+
trace!("Reseeding RNG after {} generated bytes",
128+
self.threshold - self.bytes_until_reseed);
129+
if let Err(err) = R::from_rng(&mut self.reseeder)
130+
.map(|result| self.rng = result) {
131+
let newkind = match err.kind() {
132+
a @ ErrorKind::NotReady => a,
133+
b @ ErrorKind::Transient => b,
134+
_ => {
135+
self.bytes_until_reseed = self.threshold; // skip reseeding
136+
ErrorKind::Transient
137+
}
138+
};
139+
return Err(Error::with_cause(newkind, "reseeding failed", err));
52140
}
141+
self.bytes_until_reseed = self.threshold;
142+
Ok(())
53143
}
54144
}
55145

56-
57146
impl<R: Rng+SeedableRng, Rsdr: Rng> Rng for ReseedingRng<R, Rsdr> {
58147
fn next_u32(&mut self) -> u32 {
59-
self.reseed_if_necessary();
60-
self.bytes_generated += 4;
61-
self.rng.next_u32()
148+
let value = self.rng.next_u32();
149+
self.bytes_until_reseed -= 4;
150+
if self.bytes_until_reseed <= 0 {
151+
self.reseed();
152+
}
153+
value
62154
}
63155

64156
fn next_u64(&mut self) -> u64 {
65-
self.reseed_if_necessary();
66-
self.bytes_generated += 8;
67-
self.rng.next_u64()
157+
let value = self.rng.next_u64();
158+
self.bytes_until_reseed -= 8;
159+
if self.bytes_until_reseed <= 0 {
160+
self.reseed();
161+
}
162+
value
68163
}
69164

70165
fn fill_bytes(&mut self, dest: &mut [u8]) {
71-
self.reseed_if_necessary();
72-
self.bytes_generated += dest.len() as u64;
73-
self.rng.fill_bytes(dest)
166+
self.rng.fill_bytes(dest);
167+
self.bytes_until_reseed -= dest.len() as i64;
168+
if self.bytes_until_reseed <= 0 {
169+
self.reseed();
170+
}
74171
}
75-
172+
76173
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
77-
self.reseed_if_necessary();
78-
self.bytes_generated += dest.len() as u64;
79-
self.rng.try_fill_bytes(dest)
174+
self.rng.try_fill_bytes(dest)?;
175+
self.bytes_until_reseed -= dest.len() as i64;
176+
if self.bytes_until_reseed <= 0 {
177+
self.try_reseed()?;
178+
}
179+
Ok(())
80180
}
81181
}
82182

0 commit comments

Comments
 (0)