userspace_rng/lib.rs
1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![deny(unused_must_use)]
4#![deny(unused_mut)]
5
6//! userspace-random is a rust crate that is intended to provde secure entropy to
7//! the caller even if the operating system entropy is not secure.
8//! `userspace_rng::Csprng` implements `rand_core::CryptoRng` and
9//! `rand_core::RngCore`.
10//!
11//! Usage:
12//!
13//! ```rs
14//! // Generate an ed25519 key.
15//! let mut rng = userspace_rng::Csprng{};
16//! let keypair = ed25519_dalek::Keypair::generate(&mut rng);
17//!
18//! // Generate 32 bytes of randomness.
19//! let rand_data = userspace_rng::random256();
20//! ```
21//!
22//! Modern operating systems like Linux, Mac, and Windows will all provide reliable
23//! entropy, however more niche operating systems and environments often will
24//! provide broken RNGs to the user. This concern is especially relevant to IoT and
25//! embedded hardware, where the engineers are deliberately taking as many
26//! shortcuts as they can to trim down the size and overhead of the operating
27//! system and hardware. The result may be an unintentionally compromised operating
28//! system RNG which can put users at risk.
29//!
30//! This worry has precedent. When Android was newer, a broken RNG in Android put
31//! user funds at risk: <https://bitcoin.org/en/alert/2013-08-11-android>
32//!
33//! To protect users against insecure hardware, we developed a library that
34//! generates reliable entropy in userspace. We have constructed the library to
35//! ensure that the randomness can only be compromised if both the operating system
36//! RNG is broken and also the assumptions made by our library are incorrect. Had
37//! the Android wallets used userspace-random to generate their entropy, user funds
38//! likely would have been safe despite the flaw in Android itself.
39//!
40//! This library draws entropy from CPU jitter. Specifically, the number of
41//! nanoseconds required to complete a cryptographic hash function has a high
42//! amount of variance. My own testing suggested that under normal circumstances
43//! the variance is around 100ns with a standard deviation of around 40ns. This
44//! suggests that using time elapsed during a cryptographic hashing function as a
45//! source of entropy will provide between 4 and 6 bits of entropy per measurement.
46//!
47//! When the library starts up, it performs 25 milliseconds of hashing, performing
48//! a strict minimum of 512 hashing operations total. This theoretically provides
49//! more than 2000 bits of entropy, which means there is a comfortable safety
50//! margin provided by the library. Even hardware that is more stable and reliable
51//! should be producing at least 1/4 bits of entropy per hash operation owing to
52//! the natural physical limitations of CPUs.
53//!
54//! To give some brief intuition: a CPU is a physical device that consumes
55//! electricity and produces heat. The speed at which a CPU operates (at least,
56//! with our current technology) is surprisingly dependent on factors like voltage
57//! and temperature. Further, these factors tend to vary rapidly as a CPU performs
58//! computations. Empirical measurements demonstrate that the variance is
59//! sufficiently significant to cause material variance in the total execution time
60//! of a cryptographic hash operation. Other factors such as background activity by
61//! the operating system can also play a role.
62//!
63//! For fun, this library also incorporates some ideas from the fortuna paper to
64//! protect users against an adversary that has the ability to use side channels to
65//! compromise the user entropy. In reality, these extra choices are probably not
66//! necessary to protect against real-world attackers. Fortuna was designed with a
67//! very different threat model in mind than the one we face with this library.
68//!
69//! Fortuna paper: <https://www.schneier.com/wp-content/uploads/2015/12/fortuna.pdf>
70
71use std::sync::{Mutex, Once};
72use std::time::{SystemTime, UNIX_EPOCH};
73
74use anyhow::{bail, Error};
75use getrandom::getrandom;
76use sha2::{Digest, Sha256};
77
78/// Entropy gets added to the backup pool every time the random library is used. Once every 512
79/// calls, the backup pool gets merged into the entropy pool to give the entropy pool an extra
80/// boost. As long as each call mixes at least 0.25 bits of entropy into the backup pool, any
81/// attacker that has compromised the entropy pool will lose all progress once the backup pool is
82/// mixed in.
83const BACKUP_FREQUENCY: u128 = 512;
84
85/// Shorthand to declare an issue with the system clock.
86const CLOCK_ERR: &str = "system clock could not be read";
87
88/// The entire entropy state consists of an entropy pool, a backup pool, and a counter that gets
89/// used to determine when the backup pool should be merged into the entropy pool. The counter is
90/// also used to prevent race conditions from causing duplicate entropy to be returned.
91///
92/// The entropy pool is continuously receiving small amounts of new entropy. this protects the
93/// entropy pool against side channel attacks that may be trying to learn the state of the entropy
94/// pool. if the entropy pool is compromised, the small incremental bits of entropy that get added
95/// to it will not be sufficient to let it recover.
96///
97/// The backup pool will collect a large amount of entropy before being added to the entropy pool
98/// by adding large amounts of entropy all at once, we can ensure that a compromised entropy pool
99/// can recover.
100struct EntropyState {
101 entropy_pool: [u8; 32],
102 backup_pool: [u8; 32],
103 usage_counter: u128,
104}
105
106/// All threads use the same entropy state to generate random numbers.
107static ENTROPY_STATE: Mutex<EntropyState> = Mutex::new(EntropyState {
108 entropy_pool: [0; 32],
109 backup_pool: [0; 32],
110 usage_counter: 0,
111});
112
113/// Protect the initialization function so it only runs once.
114static INIT: Once = Once::new();
115
116/// hash is a simple wrapper around the hashing function used by userspace_random.
117fn hash(b: &[u8]) -> [u8; 32] {
118 let mut hasher = Sha256::new();
119 hasher.update(b);
120 let r = hasher.finalize();
121 let mut dest = [0u8; 32];
122 dest.copy_from_slice(&r);
123 dest
124}
125
126/// Fill out the entropy pool and backup pool using entropy from the operating system then add a
127/// large amount of runtime entropy by hashing the original entropy repeatedly. Between each hash
128/// we grab the current system time and mix it into the entropy pool. The number of nanoseconds
129/// required to complete a hash is highly variable, sometimes varying as much as 200 nanoseconds
130/// between consecutive calls. this makes the hash timings an excellent source of runtime entropy
131fn init() {
132 INIT.call_once(|| {
133 // get randomness from the operating system. if the call fails we will instead start with
134 // an empty array and rely fully on the userspace random call. We panic in debug builds so
135 // that the developer knows something is wrong with the call to getrandom.
136 let mut base = [0u8; 32];
137 let mut backup = [0u8; 32];
138 match getrandom(&mut base) {
139 Ok(_) => {}
140 Err(error) => {
141 debug_assert!(false, "unable to get base randomness from OS: {}", error);
142 }
143 }
144 match getrandom(&mut backup) {
145 Ok(_) => {}
146 Err(error) => {
147 debug_assert!(false, "unable to get backup randomness from OS: {}", error);
148 }
149 }
150
151 // perform 25 milliseconds of entropy gathering. ensure that at least 512 iterations occur.
152 // This should generate anywhere between 100 and 10_000 bytes of real entropy, giving us a
153 // substantial safety margin.
154 let start = SystemTime::now();
155 let mut iters = 0;
156 while start.elapsed().expect(CLOCK_ERR).as_millis() < 25 || iters < 512 {
157 iters += 1;
158
159 // mix in the current time
160 let time = SystemTime::now()
161 .duration_since(UNIX_EPOCH)
162 .expect(CLOCK_ERR)
163 .as_nanos()
164 .to_le_bytes();
165 for i in 0..16 {
166 base[i] ^= time[i];
167 }
168 base = hash(&base);
169 }
170
171 // No userspace entropy needs to be added to the backup at init as making the entropy_pool
172 // secure is sufficient all by itself. The entropy pool is supposed to be secure, and the
173 // backup pool is supposed to reset / rescue the entropy pool if the entropy pool gets
174 // compromised. At this stage if the entropy pool is compromised then we can assume the
175 // backup pool will also be compromised.
176 let mut es = ENTROPY_STATE.lock().unwrap();
177 es.entropy_pool.copy_from_slice(&base);
178 es.backup_pool.copy_from_slice(&backup);
179 drop(es);
180 });
181}
182
183/// random256 provides 256 bits of entropy that has been hardened in userspace such that the
184/// randomness that is likely to be secure even if the underlying operating system is not properly
185/// generating secure entropy.
186///
187/// This call actively hardens the entropy pool when it is called. As a result it runs more slowly
188/// and requires three full cryptographic hash operations each time it is invoked. It also requires
189/// reading the system time 3 times. This ongoing entropy collection mechanism protects the caller
190/// against active adversaries that may be using side channels to compromise the entropy state.
191pub fn random256() -> [u8; 32] {
192 // Throughout this function we use explict values as the number of bytes instead of calling
193 // helpers like .len(). we found that being explicit made the mixing strategies easier to
194 // reason about.
195
196 init();
197
198 // Grab the latest values in the entropy state. To maximize performance the mutex is only held
199 // while the values are being read which means that multiple threads may read the same values
200 // from the entropy pool. To protect against race conditions we have a usage counter which
201 // increments on every access. Concurrent threads that read the same state will read different
202 // values for the usage counter and therefore can generate independent entropy.
203 let mut es_lock = ENTROPY_STATE.lock().unwrap();
204 let mut entropy_pool = es_lock.entropy_pool;
205 let mut backup_pool = es_lock.backup_pool;
206 let usage_counter = es_lock.usage_counter;
207 es_lock.usage_counter += 1;
208 drop(es_lock);
209 let usage_bytes: [u8; 16] = usage_counter.to_le_bytes();
210
211 // After blocking for the mutex, grab the current system time. If an attacker is the caller the
212 // attacker may be able to predict the exact value therefore this entropy is not independently
213 // sufficient to harden our entropy pool. If the caller is not an attacker, it does provide
214 // material entropy and will improve the quality of our entropy pools with negligiable
215 // computational cost
216 let start: [u8; 16] = SystemTime::now()
217 .duration_since(UNIX_EPOCH)
218 .expect(CLOCK_ERR)
219 .as_nanos()
220 .to_le_bytes();
221
222 // xor the start time into the entropy pools. We use xor to ensure that any entropy which
223 // already exists in the pools is preserved.
224 for i in 0..15 {
225 entropy_pool[i] ^= start[i];
226 backup_pool[i] ^= start[i];
227 }
228
229 // xor the usage bytes into the entropy pools. This is not to introduce entropy but rather to
230 // ensure that multiple threads that ended up using the same entropy pool value still get
231 // different random outcomes.
232 //
233 // It is very import to ensure that the usage counter is the only element that gets mixed into
234 // the final 16 bytes of the pools. If you mix in additional entropy, the entropy has a small
235 // chance of perfectly cancelling out the usage counter which would allow multiple threads to
236 // produce the exact same entropy when called.
237 for i in 16..31 {
238 entropy_pool[i] ^= usage_bytes[i - 16];
239 backup_pool[i] ^= usage_bytes[i - 16];
240 }
241
242 // We now produce the output for the caller. The output is using the entropy that was produced
243 // on the previous call to random256(). We produce the output before performing the hashing to
244 // ensure that the current entropy state does not ever reveal anything about prior outputs.
245 let output = hash(&entropy_pool);
246
247 // The number of nanosecdons that are required to complete the sha256 hashing operations is a
248 // variable number with an estimated 3-7 bits of true entropy. This entropy comes from the fact
249 // that a CPU's execution speed depends on things like its temperature and current voltage.
250 // despite what common sense may tell you, these things are highly variable even on the scale
251 // of microseconds.
252 //
253 // Because of this variance, the current system time will contain a meaningful amount of
254 // entropy. We will mix this entropy into the backup pool using xor.
255 let output_timing_entropy = SystemTime::now()
256 .duration_since(UNIX_EPOCH)
257 .expect(CLOCK_ERR)
258 .as_nanos()
259 .to_le_bytes();
260 for i in 0..15 {
261 backup_pool[i] ^= output_timing_entropy[i];
262 }
263
264 // Hash the backup pool now that new information has been xor'd in. By hashing the backup pool
265 // we guarantee that all of the new informmation gets spread evenly through the result.
266 let new_backup = hash(&backup_pool);
267
268 // We need to make sure that the entropy which gets added to the backup pool is different from
269 // the entropy that gets added to the the entropy pool. The act of hashing the backup pool has
270 // added new entropy to the system time which means we can use the current system time again to
271 // create independent entropy for the entropy pool.
272 let backup_timing_entropy = SystemTime::now()
273 .duration_since(UNIX_EPOCH)
274 .expect(CLOCK_ERR)
275 .as_nanos()
276 .to_le_bytes();
277 for i in 0..15 {
278 entropy_pool[i] ^= backup_timing_entropy[i];
279 }
280
281 // Hash the updated entropy pool so that the new entropy gets fully mixed into the pool. This
282 // hashing operation has the added benefit of adding even more entropy to the system time which
283 // is important to prevent the caller from knowing the exact timestamp that was created while
284 // hashing the backup pool.
285 let new_entropy = hash(&entropy_pool);
286
287 // Add all of our new results back into the entropy pool.
288 let mut es_lock = ENTROPY_STATE.lock().unwrap();
289 for i in 0..32 {
290 es_lock.entropy_pool[i] ^= new_entropy[i];
291 es_lock.backup_pool[i] ^= new_backup[i];
292
293 // If the backup pool has gathered enough entropy, mix the backup pool into the entropy
294 // pool. Because usage counter is incremented every time the entropy pool is read, we
295 // are guaranteed to mix in backup entropy once every 512 calls.
296 //
297 // The value of the backup pool is to reset a compromised entropy pool and restore it
298 // to full randomness.
299 if usage_counter % BACKUP_FREQUENCY == 0 {
300 es_lock.entropy_pool[i] ^= es_lock.backup_pool[i];
301 }
302 }
303 drop(es_lock);
304
305 // Return the output that was generated previously. The entropy pool has already been updated
306 // since generating the output which protects the current output even if the entropy pool is
307 // compromised in the future.
308 output
309}
310
311/// range64 returns an unbiased secure random u64 within [start, end).
312pub fn range64(start: u64, end: u64) -> Result<u64, Error> {
313 // Check that the range is valid.
314 if start >= end {
315 bail!("start must be strictly smaller than end");
316 }
317 let range = (end - start) as u128;
318
319 // Establish the 'limit', above which the rng toss is considered invalid as it will bias the
320 // result. If we get an rng toss that is above the limit, we will have to redo the rng toss.
321 // The max range is u64::MAX but the rng space is u128::MAX, so the chances of getting a result
322 // above the limit are 1/2^64 per toss in the worst case. It is cryptographically unlikely that
323 // the user needs more than two tosses.
324 let result: u64;
325 let umax = u128::MAX;
326 let limit = umax - (umax % range);
327 loop {
328 let mut base = [0u8; 16];
329 let rng = random256();
330 base.copy_from_slice(&rng[..16]);
331 let rand = u128::from_le_bytes(base);
332 if rand > limit {
333 continue;
334 }
335 result = (rand % range) as u64;
336 break;
337 }
338 Ok(result + start)
339}
340
341/// Implement a csprng for userspace-random so that it can be used for activities like generating
342/// keypairs.
343pub struct Csprng {
344 // No state is required, just use the public functions.
345}
346
347impl rand_core::CryptoRng for Csprng {}
348
349impl rand_core::RngCore for Csprng {
350 fn next_u32(&mut self) -> u32 {
351 u32::from_le_bytes(random256()[..4].try_into().unwrap())
352 }
353
354 fn next_u64(&mut self) -> u64 {
355 u64::from_le_bytes(random256()[..8].try_into().unwrap())
356 }
357
358 fn fill_bytes(&mut self, dest: &mut [u8]) {
359 let dlen = dest.len();
360 let mut i = 0;
361 while i + 32 <= dlen {
362 let rand = random256();
363 dest[i..i + 32].copy_from_slice(&rand);
364 i += 32;
365 }
366 if dlen % 32 == 0 {
367 return;
368 }
369
370 let rand = random256();
371 let need = dlen - i;
372 dest[i..dlen].copy_from_slice(&rand[..need]);
373 }
374
375 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
376 self.fill_bytes(dest);
377 Ok(())
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384 use ed25519_dalek::Signer;
385 use rand_core::RngCore;
386
387 #[test]
388 // Perform a statistical test to look for basic mistakes in random256. Note that this test
389 // passing is not an assurance that the data is truly random as lots of obviously non-random
390 // data will pass this test.
391 fn check_random256() {
392 let tries = 100_000;
393
394 // Count the number of times each byte value appears when generating random data.
395 let mut frequencies = std::collections::HashMap::new();
396 for _ in 0..tries {
397 let rand = random256();
398 for i in 0..rand.len() - 1 {
399 match frequencies.get(&rand[i]) {
400 Some(num) => frequencies.insert(rand[i], num + 1),
401 None => frequencies.insert(rand[i], 1),
402 };
403 }
404 }
405
406 // Review the number of appearances of each byte value and look for statistical anomalies.
407 for i in 0..=255 {
408 let num = frequencies.get(&i).unwrap();
409 assert!(num > &(tries * 32 * 80 / 256 / 100));
410 assert!(num < &(tries * 32 * 112 / 256 / 100));
411 }
412 }
413
414 #[test]
415 fn check_range64() {
416 let tries = 10_000;
417 for _ in 0..tries {
418 let i = range64(0, 1).unwrap();
419 assert!(i == 0);
420 let i = range64(1, 2).unwrap();
421 assert!(i == 1);
422 range64(1, 1).unwrap_err();
423 range64(1, 0).unwrap_err();
424 }
425
426 // Get a range of 256 and count the frequencies of each result, looking for statistical
427 // anomalies. This isn't a robust statistcal test, it is just designed to catch obvious
428 // errors such as off-by-one.
429 let tries = 200_000;
430 let mut frequencies = std::collections::HashMap::new();
431 for _ in 0..tries {
432 let rand = range64(1, 256).unwrap();
433 match frequencies.get(&rand) {
434 Some(num) => frequencies.insert(rand, num + 1),
435 None => frequencies.insert(rand, 1),
436 };
437 }
438 for i in 1..256 {
439 let num = frequencies.get(&i).unwrap();
440 if *num < tries / 256 * 80 / 100 {
441 panic!(
442 "value {} appeared fewer times than expected: {} :: {}",
443 i,
444 num,
445 tries / 256 * 80 / 100
446 );
447 }
448 if *num > tries / 256 * 125 / 100 {
449 panic!(
450 "value {} appeared greater times than expected: {} :: {}",
451 i,
452 num,
453 tries / 256 * 125 / 100
454 );
455 }
456 }
457 }
458
459 #[test]
460 fn check_prng_impl() {
461 // Try fill_bytes with arrays of different sizes and make sure they all get filled.
462 let mut csprng = Csprng {};
463 let mut counter = std::collections::HashMap::new();
464 let mut total_bytes = 0;
465 for i in 1..100 {
466 let mut bytes = vec![0u8; i];
467 csprng.fill_bytes(&mut bytes);
468 let base = bytes.clone();
469 let mut different = false;
470 for _ in 0..32 {
471 csprng.fill_bytes(&mut bytes);
472 if bytes != base {
473 different = true;
474 }
475 for i in 0..bytes.len() {
476 let b = bytes[i];
477 match counter.get(&b) {
478 None => counter.insert(b, 1),
479 Some(v) => counter.insert(b, v+1),
480 };
481 total_bytes += 1;
482 }
483 }
484 assert!(different);
485 }
486 assert!(counter.len() == 256);
487 for i in 0..=255 {
488 let v = *counter.get(&i).unwrap();
489 assert!((v as f64) > total_bytes as f64 * 0.7 / 256.0);
490 assert!((v as f64) < total_bytes as f64 * 1.3 / 256.0);
491 }
492
493
494 // Basic test: see that we can use our csprng to create an ed25519 key.
495 let keypair = ed25519_dalek::Keypair::generate(&mut csprng);
496 let msg = b"example message";
497 let sig = keypair.sign(msg);
498 keypair.public.verify_strict(msg, &sig).unwrap();
499
500 // Secondary test: ensure two keys made with the Csprng are not identical.
501 let mut csprng = Csprng {};
502 let keypair2 = ed25519_dalek::Keypair::generate(&mut csprng);
503 assert!(keypair.to_bytes() != keypair2.to_bytes());
504
505 // Use all of the methods of the cspring.
506 let mut counter = std::collections::HashMap::new();
507 for _ in 0..10_000 {
508 let t = csprng.next_u32() as u64;
509 match counter.get(&t) {
510 None => counter.insert(t, 1),
511 Some(v) => counter.insert(t, v + 1),
512 };
513 }
514 for _ in 0..10_000 {
515 let t = csprng.next_u64();
516 match counter.get(&t) {
517 None => counter.insert(t, 1),
518 Some(v) => counter.insert(t, v + 1),
519 };
520 }
521
522 for _ in 0..100 {
523 let mut bytes = [0u8; 8008];
524 csprng.fill_bytes(&mut bytes);
525 for i in 0..1001 {
526 let t = u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap());
527 match counter.get(&t) {
528 None => counter.insert(t, 1),
529 Some(v) => counter.insert(t, v + 1),
530 };
531 }
532 let mut bytes = [0u8; 8008];
533 csprng.try_fill_bytes(&mut bytes).unwrap();
534 for i in 0..1001 {
535 let t = u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap());
536 match counter.get(&t) {
537 None => counter.insert(t, 1),
538 Some(v) => counter.insert(t, v + 1),
539 };
540 }
541 let mut bytes = [0u8; 32];
542 csprng.fill_bytes(&mut bytes);
543 for i in 0..1 {
544 let t = u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap());
545 match counter.get(&t) {
546 None => counter.insert(t, 1),
547 Some(v) => counter.insert(t, v + 1),
548 };
549 }
550 let mut bytes = [0u8; 64];
551 csprng.try_fill_bytes(&mut bytes).unwrap();
552 for i in 0..2 {
553 let t = u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap());
554 match counter.get(&t) {
555 None => counter.insert(t, 1),
556 Some(v) => counter.insert(t, v + 1),
557 };
558 }
559 let mut bytes = [0u8; 128];
560 csprng.fill_bytes(&mut bytes);
561 for i in 0..1 {
562 let t = u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap());
563 match counter.get(&t) {
564 None => counter.insert(t, 1),
565 Some(v) => counter.insert(t, v + 1),
566 };
567 }
568 let mut bytes = [0u8; 56];
569 csprng.try_fill_bytes(&mut bytes).unwrap();
570 for i in 0..2 {
571 let t = u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap());
572 match counter.get(&t) {
573 None => counter.insert(t, 1),
574 Some(v) => counter.insert(t, v + 1),
575 };
576 }
577 }
578
579 for (key, value) in counter {
580 if value > 10 {
581 panic!("distribution does not appear to be even: {}:{}", key, value);
582 }
583 }
584 }
585}