Insecure generation of random numbers
Need
Generation of security tokens with a cryptographically secure random number generator
Context
• Usage of Rust 1.75+ for building application services
• Usage of the actix-web crate for handling HTTP requests and cookies
• Usage of the rand crate for random number generation
Description
1. Non compliant code
use actix_web::cookie::Cookie;
use actix_web::{post, HttpResponse};
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
#[post("/login")]
async fn login() -> HttpResponse {
// SmallRng is fast but predictable: its output can be reconstructed...The `login` handler below creates the session identifier with `SmallRng`, a random number generator from the `rand` crate, and sends it to the browser in the `session_id` cookie. `SmallRng` is designed for speed in simulations and games, not for security. Its internal state is small and its algorithm is public, so an attacker who observes a few outputs can reconstruct the state and predict the identifiers issued to other users. The identifier also has only 64 bits, taken from a single `u64`. With a predictable session identifier, an attacker can hijack sessions without stealing any cookie. The same weakness appears when a generator is created with a fixed seed, such as `StdRng::seed_from_u64(987654321)`, and then used for keys, tokens or nonces: every run of the program produces the same values.
2. Steps
• Generate session identifiers, API tokens, password reset codes, keys and nonces with `rand::rngs::OsRng` or `rand::thread_rng()`.
• Do not use `SmallRng`, or any generator built for speed, for values that an attacker must not guess.
• Never create a generator with a fixed seed, such as `StdRng::seed_from_u64(...)`, outside of tests.
• Use at least 128 bits of entropy for tokens; 32 random bytes give 256 bits.
• Encode token bytes with URL-safe Base64 or hexadecimal instead of reducing them to a single integer.
3. Secure code example
use actix_web::cookie::{Cookie, SameSite};
use actix_web::{post, HttpResponse};
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use rand::rngs::OsRng;
use rand::RngCore;
fn new_session_id() -> String {...The corrected handler builds the session identifier in `new_session_id`, which fills 32 bytes, 256 bits of entropy, from `OsRng`. `OsRng` reads from the operating system cryptographically secure generator (`getrandom` on Linux, `SecRandomCopyBytes` on macOS), so its output cannot be predicted from previous values. The bytes are encoded with URL-safe Base64 without padding, which produces a compact value that is valid in a cookie. The cookie is also marked `HttpOnly`, `Secure` and `SameSite=Strict`, so scripts cannot read it and browsers only send it over HTTPS to the same site. For cryptographic keys, the same rule applies: pass `OsRng` or `rand::thread_rng()` to the key generation function, and never create a generator from a fixed seed outside of tests.
References
• 034. Insecure generation of random numbers