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}