//! Quantum password/passphrase generation from QTRNG bytes. //! //! Configurable length and character set (lowercase, uppercase, digits, symbols). /// Default password length. pub const DEFAULT_LENGTH: usize = 16; /// Max length per request. pub const MAX_LENGTH: usize = 128; /// Character set presets (no spaces; safe for passwords). pub const CHARSET_LOWER: &str = "abcdefghijklmnopqrstuvwxyz"; pub const CHARSET_UPPER: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; pub const CHARSET_DIGIT: &str = "0123456789"; pub const CHARSET_SYMBOL: &str = "!@#$%^&*()-_=+[]{}|;:,.<>?"; /// Build charset from flags: lower, upper, digit, symbol. pub fn charset_from_flags(lower: bool, upper: bool, digit: bool, symbol: bool) -> Vec { let mut v = Vec::new(); if lower { v.extend_from_slice(CHARSET_LOWER.as_bytes()); } if upper { v.extend_from_slice(CHARSET_UPPER.as_bytes()); } if digit { v.extend_from_slice(CHARSET_DIGIT.as_bytes()); } if symbol { v.extend_from_slice(CHARSET_SYMBOL.as_bytes()); } if v.is_empty() { v.extend_from_slice(CHARSET_LOWER.as_bytes()); v.extend_from_slice(CHARSET_DIGIT.as_bytes()); } v } /// "alphanumeric" = lower + upper + digit pub fn charset_alphanumeric() -> Vec { charset_from_flags(true, true, true, false) } /// "full" = lower + upper + digit + symbol pub fn charset_full() -> Vec { charset_from_flags(true, true, true, true) } /// "hex" = 0-9a-f pub fn charset_hex() -> Vec { b"0123456789abcdef".to_vec() } /// Generate a password of `length` from random `bytes` using the given charset. /// Charset must be non-empty. Returns `None` if not enough bytes or invalid params. pub fn generate_password(length: usize, charset: &[u8], bytes: &[u8]) -> Option { if length == 0 || length > MAX_LENGTH || charset.is_empty() { return None; } let n = charset.len() as u32; let threshold = (u32::MAX / n) * n; let mut out = String::with_capacity(length); let mut idx = 0; for _ in 0..length { loop { if idx + 4 > bytes.len() { return None; } let word = u32::from_be_bytes([ bytes[idx], bytes[idx + 1], bytes[idx + 2], bytes[idx + 3], ]); idx += 4; if word < threshold { let i = (word % n) as usize; out.push(charset[i] as char); break; } } } Some(out) } /// Estimate bytes needed for a password of given length with charset size. #[allow(dead_code)] pub fn bytes_needed(length: usize, charset_len: usize) -> usize { if charset_len == 0 { return 0; } let n = charset_len as u32; let threshold = (u32::MAX / n) * n; let accept_prob = threshold as f64 / u32::MAX as f64; (4.0_f64 / accept_prob * length as f64).ceil() as usize } /// Characters that look ambiguous in many fonts. pub const AMBIGUOUS_CHARS: &[u8] = b"0OoIl1|"; /// Remove ambiguous characters from a charset. pub fn filter_ambiguous(charset: &[u8]) -> Vec { charset .iter() .copied() .filter(|c| !AMBIGUOUS_CHARS.contains(c)) .collect() }