From 24b9cc38ea81b3bbbb17e7e479d798c48081a158 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 28 Oct 2024 14:20:59 +0000 Subject: [PATCH] README: rand is not a crypto library (#1514) Closes #1358 by documenting what Rand is not. Co-authored-by: Dan --- README.md | 40 +++++++++++++++++-------------- SECURITY.md | 43 ++++++++++++++++++++++----------- rand_core/src/os.rs | 8 +++---- src/distr/uniform.rs | 48 +++++++++++++++++++------------------ src/distr/uniform_float.rs | 17 +++++++------ src/distr/uniform_int.rs | 7 ++++++ src/rngs/thread.rs | 49 +++++++++++++++++++++++++------------- 7 files changed, 130 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 1dbe4c55f1..58f363896b 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -Rand is a Rust library supporting random generators: +Rand is a set of crates supporting (pseudo-)random generators: -- A standard RNG trait: [`rand_core::RngCore`](https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html) -- Fast implementations of the best-in-class [cryptographic](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and - [non-cryptographic](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators: [`rand::rngs`](https://docs.rs/rand/latest/rand/rngs/index.html), and more RNGs: [`rand_chacha`](https://docs.rs/rand_chacha), [`rand_xoshiro`](https://docs.rs/rand_xoshiro/), [`rand_pcg`](https://docs.rs/rand_pcg/), [rngs repo](https://github.com/rust-random/rngs/) -- [`rand::rng`](https://docs.rs/rand/latest/rand/fn.rng.html) is an asymtotically-fast, reasonably secure generator available on all `std` targets -- Secure seeding via the [`getrandom` crate](https://crates.io/crates/getrandom) +- Built over a standard RNG trait: [`rand_core::RngCore`](https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html) +- With fast implementations of both [strong](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and + [small](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators: [`rand::rngs`](https://docs.rs/rand/latest/rand/rngs/index.html), and more RNGs: [`rand_chacha`](https://docs.rs/rand_chacha), [`rand_xoshiro`](https://docs.rs/rand_xoshiro/), [`rand_pcg`](https://docs.rs/rand_pcg/), [rngs repo](https://github.com/rust-random/rngs/) +- [`rand::rng`](https://docs.rs/rand/latest/rand/fn.rng.html) is an asymptotically-fast, automatically-seeded and reasonably strong generator available on all `std` targets +- Direct support for seeding generators from the [`getrandom` crate](https://crates.io/crates/getrandom) -Supporting random value generation and random processes: +With broad support for random value generation and random processes: -- [`Standard`](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html) random value generation -- Ranged [`Uniform`](https://docs.rs/rand/latest/rand/distributions/struct.Uniform.html) number generation for many types -- A flexible [`distributions`](https://docs.rs/rand/*/rand/distr/index.html) module -- Samplers for a large number of random number distributions via our own +- [`Standard`](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html) random value sampling, + [`Uniform`](https://docs.rs/rand/latest/rand/distributions/struct.Uniform.html)-ranged value sampling + and [more](https://docs.rs/rand/latest/rand/distr/index.html) +- Samplers for a large number of non-uniform random number distributions via our own [`rand_distr`](https://docs.rs/rand_distr) and via the [`statrs`](https://docs.rs/statrs/0.13.0/statrs/) - Random processes (mostly choose and shuffle) via [`rand::seq`](https://docs.rs/rand/latest/rand/seq/index.html) traits @@ -28,19 +28,23 @@ All with: - [Portably reproducible output](https://rust-random.github.io/book/portability.html) - `#[no_std]` compatibility (partial) -- *Many* performance optimisations +- *Many* performance optimisations thanks to contributions from the wide + user-base -It's also worth pointing out what Rand *is not*: +Rand **is not**: -- Small. Most low-level crates are small, but the higher-level `rand` and - `rand_distr` each contain a lot of functionality. +- Small (LOC). Most low-level crates are small, but the higher-level `rand` + and `rand_distr` each contain a lot of functionality. - Simple (implementation). We have a strong focus on correctness, speed and flexibility, but not simplicity. If you prefer a small-and-simple library, there are alternatives including [fastrand](https://crates.io/crates/fastrand) and [oorandom](https://crates.io/crates/oorandom). -- Slow. We take performance seriously, with considerations also for set-up - time of new distributions, commonly-used parameters, and parameters of the - current sampler. +- A cryptography library. Rand provides functionality for generating + unpredictable random data (potentially applicable depending on requirements) + but does not provide high-level cryptography functionality. + +Rand is a community project and cannot provide legally-binding guarantees of +security. Documentation: diff --git a/SECURITY.md b/SECURITY.md index 356fbe879d..26cf7c12fc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,31 +1,46 @@ # Security Policy -## No guarantees +## Disclaimer -Support is provided on a best-effort bases only. -No binding guarantees can be provided. +Rand is a community project and cannot provide legally-binding guarantees of +security. ## Security premises -Rand provides the trait `rand_core::CryptoRng` aka `rand::CryptoRng` as a marker -trait. Generators implementing `RngCore` *and* `CryptoRng`, and given the -additional constraints that: +### Marker traits + +Rand provides the marker traits `CryptoRng`, `TryCryptoRng` and +`CryptoBlockRng`. Generators implementing one of these traits and used in a way +which meets the following additional constraints: - Instances of seedable RNGs (those implementing `SeedableRng`) are constructed with cryptographically secure seed values -- The state (memory) of the RNG and its seed value are not be exposed +- The state (memory) of the RNG and its seed value are not exposed are expected to provide the following: -- An attacker can gain no advantage over chance (50% for each bit) in - predicting the RNG output, even with full knowledge of all prior outputs. +- An attacker cannot predict the output with more accuracy than what would be + expected through pure chance since each possible output value of any method + under the above traits which generates output bytes (including + `RngCore::next_u32`, `RngCore::next_u64`, `RngCore::fill_bytes`, + `TryRngCore::try_next_u32`, `TryRngCore::try_next_u64`, + `TryRngCore::try_fill_bytes` and `BlockRngCore::generate`) should be equally + likely +- Knowledge of prior outputs from the generator does not aid an attacker in + predicting future outputs + +### Specific generators + +`OsRng` is a stateless "generator" implemented via [getrandom]. As such, it has +no possible state to leak and cannot be improperly seeded. + +`ThreadRng` will periodically reseed itself, thus placing an upper bound on the +number of bits of output from an instance before any advantage an attacker may +have gained through state-compromising side-channel attacks is lost. -For some RNGs, notably `OsRng`, `ThreadRng` and those wrapped by `ReseedingRng`, -we provide limited mitigations against side-channel attacks: +[getrandom]: https://crates.io/crates/getrandom -- After the state (memory) of an RNG is leaked, there is an upper-bound on the - number of bits of output by the RNG before prediction of output by an - observer again becomes computationally-infeasible +### Distributions Additionally, derivations from such an RNG (including the `Rng` trait, implementations of the `Distribution` trait, and `seq` algorithms) should not diff --git a/rand_core/src/os.rs b/rand_core/src/os.rs index 86ae462e5a..78b689bc02 100644 --- a/rand_core/src/os.rs +++ b/rand_core/src/os.rs @@ -11,10 +11,9 @@ use crate::{TryCryptoRng, TryRngCore}; use getrandom::getrandom; -/// A random number generator that retrieves randomness from the -/// operating system. +/// An interface over the operating-system's random data source /// -/// This is a zero-sized struct. It can be freely constructed with `OsRng`. +/// This is a zero-sized struct. It can be freely constructed with just `OsRng`. /// /// The implementation is provided by the [getrandom] crate. Refer to /// [getrandom] documentation for details. @@ -32,7 +31,8 @@ use getrandom::getrandom; /// /// After the first successful call, it is highly unlikely that failures or /// significant delays will occur (although performance should be expected to -/// be much slower than a user-space PRNG). +/// be much slower than a user-space +/// [PRNG](https://rust-random.github.io/book/guide-gen.html#pseudo-random-number-generators)). /// /// # Usage example /// ``` diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 2ab4f56b82..ac3e1676ad 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -11,17 +11,7 @@ //! //! [`Uniform`] is the standard distribution to sample uniformly from a range; //! e.g. `Uniform::new_inclusive(1, 6).unwrap()` can sample integers from 1 to 6, like a -//! standard die. [`Rng::random_range`] supports any type supported by [`Uniform`]. -//! -//! This distribution is provided with support for several primitive types -//! (all integer and floating-point types) as well as [`std::time::Duration`], -//! and supports extension to user-defined types via a type-specific *back-end* -//! implementation. -//! -//! The types [`UniformInt`], [`UniformFloat`] and [`UniformDuration`] are the -//! back-ends supporting sampling from primitive integer and floating-point -//! ranges as well as from [`std::time::Duration`]; these types do not normally -//! need to be used directly (unless implementing a derived back-end). +//! standard die. [`Rng::random_range`] is implemented over [`Uniform`]. //! //! # Example usage //! @@ -151,26 +141,38 @@ use serde::{Deserialize, Serialize}; /// Sample values uniformly between two bounds. /// +/// # Construction +/// /// [`Uniform::new`] and [`Uniform::new_inclusive`] construct a uniform -/// distribution sampling from the given range; these functions may do extra -/// work up front to make sampling of multiple values faster. If only one sample -/// from the range is required, [`Rng::random_range`] can be more efficient. +/// distribution sampling from the given `low` and `high` limits. `Uniform` may +/// also be constructed via [`TryFrom`] as in `Uniform::try_from(1..=6).unwrap()`. +/// +/// Constructors may do extra work up front to allow faster sampling of multiple +/// values. Where only a single sample is required it is suggested to use +/// [`Rng::random_range`] or one of the `sample_single` methods instead. /// /// When sampling from a constant range, many calculations can happen at /// compile-time and all methods should be fast; for floating-point ranges and /// the full range of integer types, this should have comparable performance to /// the `Standard` distribution. /// -/// Steps are taken to avoid bias, which might be present in naive -/// implementations; for example `rng.gen::() % 170` samples from the range -/// `[0, 169]` but is twice as likely to select numbers less than 85 than other -/// values. Further, the implementations here give more weight to the high-bits -/// generated by the RNG than the low bits, since with some RNGs the low-bits -/// are of lower quality than the high bits. +/// # Provided implementations /// -/// Implementations must sample in `[low, high)` range for -/// `Uniform::new(low, high)`, i.e., excluding `high`. In particular, care must -/// be taken to ensure that rounding never results values `< low` or `>= high`. +/// - `char` ([`UniformChar`]): samples a range over the implementation for `u32` +/// - `f32`, `f64` ([`UniformFloat`]): samples approximately uniformly within a +/// range; bias may be present in the least-significant bit of the significand +/// and the limits of the input range may be sampled even when an open +/// (exclusive) range is used +/// - Integer types ([`UniformInt`]) may show a small bias relative to the +/// expected uniform distribution of output. In the worst case, bias affects +/// 1 in `2^n` samples where n is 56 (`i8` and `u8`), 48 (`i16` and `u16`), 96 +/// (`i32` and `u32`), 64 (`i64` and `u64`), 128 (`i128` and `u128`). +/// The `unbiased` feature flag fixes this bias. +/// - `usize` ([`UniformUsize`]) is handled specially, using the `u32` +/// implementation where possible to enable portable results across 32-bit and +/// 64-bit CPU architectures. +/// - `Duration` ([`UniformDuration`]): samples a range over the implementation +/// for `u32` or `u64` /// /// # Example /// diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index 82fd68bbc8..99bf2bf601 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -29,14 +29,17 @@ use serde::{Deserialize, Serialize}; /// /// # Implementation notes /// -/// Instead of generating a float in the `[0, 1)` range using [`Standard`], the -/// `UniformFloat` implementation converts the output of an PRNG itself. This -/// way one or two steps can be optimized out. +/// `UniformFloat` implementations convert RNG output to a float in the range +/// `[1, 2)` via transmutation, map this to `[0, 1)`, then scale and translate +/// to the desired range. Values produced this way have what equals 23 bits of +/// random digits for an `f32` and 52 for an `f64`. /// -/// The floats are first converted to a value in the `[1, 2)` interval using a -/// transmute-based method, and then mapped to the expected range with a -/// multiply and addition. Values produced this way have what equals 23 bits of -/// random digits for an `f32`, and 52 for an `f64`. +/// # Bias and range errors +/// +/// Bias may be expected within the least-significant bit of the significand. +/// It is not guaranteed that exclusive limits of a range are respected; i.e. +/// when sampling the range `[a, b)` it is not guaranteed that `b` is never +/// sampled. /// /// [`new`]: UniformSampler::new /// [`new_inclusive`]: UniformSampler::new_inclusive diff --git a/src/distr/uniform_int.rs b/src/distr/uniform_int.rs index b53ca367b9..4fe07707bd 100644 --- a/src/distr/uniform_int.rs +++ b/src/distr/uniform_int.rs @@ -58,6 +58,13 @@ use serde::{Deserialize, Serialize}; /// multiply by `range`, the result is in the high word. Then comparing the low /// word against `zone` makes sure our distribution is uniform. /// +/// # Bias +/// +/// Unless the `unbiased` feature flag is used, outputs may have a small bias. +/// In the worst case, bias affects 1 in `2^n` samples where n is +/// 56 (`i8` and `u8`), 48 (`i16` and `u16`), 96 (`i32` and `u32`), 64 (`i64` +/// and `u64`), 128 (`i128` and `u128`). +/// /// [`Uniform`]: super::Uniform #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index fca961f532..64ca0e1f76 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -41,16 +41,36 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// A reference to the thread-local generator /// /// This type is a reference to a lazily-initialized thread-local generator. -/// An instance can be obtained via [`rand::rng()`][crate::rng())] or via -/// `ThreadRng::default()`. +/// An instance can be obtained via [`rand::rng()`][crate::rng()] or via +/// [`ThreadRng::default()`]. /// The handle cannot be passed between threads (is not `Send` or `Sync`). /// -/// `ThreadRng` uses the same CSPRNG as [`StdRng`], ChaCha12. As with -/// [`StdRng`], the algorithm may be changed, subject to reasonable expectations -/// of security and performance. +/// # Security /// -/// `ThreadRng` is automatically seeded from [`OsRng`] with periodic reseeding -/// (every 64 kiB — see [`ReseedingRng`] documentation for details). +/// Security must be considered relative to a threat model and validation +/// requirements. The Rand project can provide no guarantee of fitness for +/// purpose. The design criteria for `ThreadRng` are as follows: +/// +/// - Automatic seeding via [`OsRng`] and periodically thereafter (see +/// ([`ReseedingRng`] documentation). Limitation: there is no automatic +/// reseeding on process fork (see [below](#fork)). +/// - A rigorusly analyzed, unpredictable (cryptographic) pseudo-random generator +/// (see [the book on security](https://rust-random.github.io/book/guide-rngs.html#security)). +/// The currently selected algorithm is ChaCha (12-rounds). +/// See also [`StdRng`] documentation. +/// - Not to leak internal state through [`Debug`] or serialization +/// implementations. +/// - No further protections exist to in-memory state. In particular, the +/// implementation is not required to zero memory on exit (of the process or +/// thread). (This may change in the future.) +/// - Be fast enough for general-purpose usage. Note in particular that +/// `ThreadRng` is designed to be a "fast, reasonably secure generator" +/// (where "reasonably secure" implies the above criteria). +/// +/// We leave it to the user to determine whether this generator meets their +/// security requirements. For an alternative, see [`OsRng`]. +/// +/// # Fork /// /// `ThreadRng` is not automatically reseeded on fork. It is recommended to /// explicitly call [`ThreadRng::reseed`] immediately after a fork, for example: @@ -68,13 +88,6 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// from an interrupt (e.g. a fork handler) unless it can be guaranteed that no /// other method on the same `ThreadRng` is currently executing. /// -/// Security must be considered relative to a threat model and validation -/// requirements. `ThreadRng` attempts to meet basic security considerations -/// for producing unpredictable random numbers: use a CSPRNG, use a -/// recommended platform-specific seed ([`OsRng`]), and avoid -/// leaking internal secrets e.g. via [`Debug`] implementation or serialization. -/// Memory is not zeroized on drop. -/// /// [`ReseedingRng`]: crate::rngs::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng #[derive(Clone)] @@ -115,9 +128,9 @@ thread_local!( } ); -/// Access a local, pre-initialized generator +/// Access a fast, pre-initialized generator /// -/// This is a reasonably fast unpredictable thread-local instance of [`ThreadRng`]. +/// This is a handle to the local [`ThreadRng`]. /// /// See also [`crate::rngs`] for alternatives. /// @@ -139,6 +152,10 @@ thread_local!( /// println!("A simulated die roll: {}", rng.random_range(1..=6)); /// # } /// ``` +/// +/// # Security +/// +/// Refer to [`ThreadRng#Security`]. pub fn rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); ThreadRng { rng }