logo

Database

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.