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 { fn generate_code() -> String {
use std::collections::hash_map::DefaultHasher; // UUID v4 uses getrandom (backed by /dev/urandom on Linux, BCryptGenRandom
use std::hash::{Hash, Hasher}; // on Windows) — a CSPRNG. We extract 4 bytes from it for a uniform random
use std::time::SystemTime; // number in [0, 1_000_000).
let uuid = uuid::Uuid::new_v4();
let mut hasher = DefaultHasher::new(); let bytes = uuid.as_bytes();
SystemTime::now().hash(&mut hasher); let raw = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
std::process::id().hash(&mut hasher);
let raw = hasher.finish();
format!("{:06}", raw % 1_000_000) format!("{:06}", raw % 1_000_000)
} }
@ -314,6 +312,15 @@ mod tests {
assert!(code.chars().all(|c| c.is_ascii_digit())); 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] #[test]
fn generate_token_has_prefix() { fn generate_token_has_prefix() {
let token = generate_token(); let token = generate_token();