fix: use CSPRNG for pairing code generation

Replace DefaultHasher + SystemTime + process::id() with UUID v4
(backed by getrandom/urandom CSPRNG) for pairing code generation.

The previous implementation used predictable entropy sources
(system time to ~1s precision and process ID) with a non-cryptographic
hash (SipHash), making the 6-digit code brute-forceable.

The new implementation extracts 4 random bytes from a UUID v4
(which uses the OS CSPRNG) and derives the 6-digit code from those.
No new dependencies added — reuses existing uuid crate.

Adds a test verifying non-deterministic output.

Ref: CWE-330 (Use of Insufficiently Random Values)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Víctor R. Escobar 2026-02-14 13:29:58 +01:00
parent ac540d2b63
commit 15a58eb7da

View file

@ -145,16 +145,14 @@ impl PairingGuard {
}
}
/// Generate a 6-digit numeric pairing code.
/// Generate a 6-digit numeric pairing code using cryptographically secure randomness.
fn generate_code() -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::time::SystemTime;
let mut hasher = DefaultHasher::new();
SystemTime::now().hash(&mut hasher);
std::process::id().hash(&mut hasher);
let raw = hasher.finish();
// UUID v4 uses getrandom (backed by /dev/urandom on Linux, BCryptGenRandom
// on Windows) — a CSPRNG. We extract 4 bytes from it for a uniform random
// number in [0, 1_000_000).
let uuid = uuid::Uuid::new_v4();
let bytes = uuid.as_bytes();
let raw = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
format!("{:06}", raw % 1_000_000)
}
@ -314,6 +312,15 @@ mod tests {
assert!(code.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn generate_code_is_not_deterministic() {
// Two codes generated in the same process should differ (with overwhelming
// probability — collision chance is 1 in 1,000,000).
let c1 = generate_code();
let c2 = generate_code();
assert_ne!(c1, c2, "Two consecutive codes should differ (CSPRNG)");
}
#[test]
fn generate_token_has_prefix() {
let token = generate_token();