From 15a58eb7da878de0f8b178f0fe49a0ec1ef81529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20R=2E=20Escobar?= Date: Sat, 14 Feb 2026 13:29:58 +0100 Subject: [PATCH] fix: use CSPRNG for pairing code generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/security/pairing.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/security/pairing.rs b/src/security/pairing.rs index 08c99af..814f951 100644 --- a/src/security/pairing.rs +++ b/src/security/pairing.rs @@ -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();