Skip to content

Commit fa37f10

Browse files
committed
Port ReseedingRng changes
1 parent 5f2686a commit fa37f10

File tree

1 file changed

+134
-33
lines changed

1 file changed

+134
-33
lines changed

src/reseeding.rs

Lines changed: 134 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,59 @@
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 PRNG's can have very small periods of 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 there is found a cryptographic weakness in the used CSPRNG,
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 seemingly
40+
/// strong, does not come with a security proof and does not meet the current
41+
/// standards for a cryptographically secure PRNG. By reseeding it very
42+
/// frequently (every 32 MiB) it seems safe to assume there is no attack that
43+
/// 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 methods `ReseedingRng` wil not panic, but try to handle the
49+
/// error intelligently. And if nothing helps, continue without reseeding.
50+
///
51+
/// It is usually best to use the infallible methods `next_u32`, `next_u64` and
52+
/// `fill_bytes` because they can make use of this error handeling strategy.
53+
///
54+
/// Use `try_fill_bytes` and possible `try_reseed` if you want to handle
55+
/// reseeding errors explicitly. All reseeding errors will either be
56+
/// `ErrorKind::Transient` or `ErrorKind::NotReady`, and contain the original
57+
/// error as cause.
1858
#[derive(Debug)]
1959
pub struct ReseedingRng<R, Rsdr> {
2060
rng: R,
21-
generation_threshold: u64,
22-
bytes_generated: u64,
23-
/// Controls the behaviour when reseeding the RNG.
2461
reseeder: Rsdr,
62+
threshold: i64,
63+
bytes_until_reseed: i64,
2564
}
2665

2766
impl<R: Rng+SeedableRng, Rsdr: Rng> ReseedingRng<R, Rsdr> {
@@ -30,52 +69,114 @@ impl<R: Rng+SeedableRng, Rsdr: Rng> ReseedingRng<R, Rsdr> {
3069
/// # Arguments
3170
///
3271
/// * `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> {
72+
/// * `threshold`: the number of generated bytes after which to reseed the RNG.
73+
/// * `reseeder`: the RNG to use for reseeding.
74+
pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> ReseedingRng<R,Rsdr> {
75+
assert!(threshold <= ::core::i64::MAX as u64);
3676
ReseedingRng {
3777
rng: rng,
38-
generation_threshold: generation_threshold,
39-
bytes_generated: 0,
40-
reseeder: reseeder
78+
reseeder: reseeder,
79+
threshold: threshold as i64,
80+
bytes_until_reseed: threshold as i64,
81+
}
82+
}
83+
84+
/// Reseed the internal RNG.
85+
///
86+
/// On error, this will try to intelligently handle reseeding. If the error
87+
/// kind indicates retrying might help, it will immidiately retry a couple
88+
/// of times. If the error kind indicates the seeding RNG is not ready, it
89+
/// will retry after a while, after `threshold / 256` generated bytes.
90+
///
91+
/// If the seeding RNG has an other error or a permanently failure, it will
92+
/// completely skip reseeding. Only after generating `threshold` bytes it
93+
/// will retry again.
94+
#[inline(never)]
95+
pub fn reseed(&mut self) {
96+
trace!("Reseeding RNG after {} generated bytes",
97+
self.threshold - self.bytes_until_reseed);
98+
self.bytes_until_reseed = self.threshold;
99+
let mut err_count = 0;
100+
loop {
101+
if let Err(e) = R::from_rng(&mut self.reseeder)
102+
.map(|result| self.rng = result) {
103+
let kind = e.kind();
104+
if kind.should_wait() {
105+
self.bytes_until_reseed = self.threshold >> 8;
106+
info!("Reseeding delayed, retrying after {} generated bytes",
107+
self.bytes_until_reseed);
108+
} else if kind.should_retry() {
109+
err_count += 1;
110+
// Retry immediately for 5 times (arbitrary limit)
111+
if err_count <= 5 { continue; }
112+
}
113+
info!("Reseeding failed, RNG remains unchanged. Error: {}", e);
114+
}
115+
break; // Successfully reseeded, delayed, or given up.
41116
}
42117
}
43118

44119
/// Reseed the internal RNG if the number of bytes that have been
45120
/// 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).unwrap();
50-
self.bytes_generated = 0;
121+
///
122+
/// If reseeding fails, return an error with the original cause. Note that
123+
/// if the cause has a permanent failure, we report a transient error and
124+
/// skip reseeding.
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));
51140
}
141+
self.bytes_until_reseed = self.threshold;
142+
Ok(())
52143
}
53144
}
54145

55-
56146
impl<R: Rng+SeedableRng, Rsdr: Rng> Rng for ReseedingRng<R, Rsdr> {
57147
fn next_u32(&mut self) -> u32 {
58-
self.reseed_if_necessary();
59-
self.bytes_generated += 4;
60-
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
61154
}
62155

63156
fn next_u64(&mut self) -> u64 {
64-
self.reseed_if_necessary();
65-
self.bytes_generated += 8;
66-
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
67163
}
68164

69165
fn fill_bytes(&mut self, dest: &mut [u8]) {
70-
self.reseed_if_necessary();
71-
self.bytes_generated += dest.len() as u64;
72-
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+
}
73171
}
74-
172+
75173
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
76-
self.reseed_if_necessary();
77-
self.bytes_generated += dest.len() as u64;
78-
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(())
79180
}
80181
}
81182

0 commit comments

Comments
 (0)