camera-trng/src/tools/password.rs

109 lines
3.2 KiB
Rust

//! 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<u8> {
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<u8> {
charset_from_flags(true, true, true, false)
}
/// "full" = lower + upper + digit + symbol
pub fn charset_full() -> Vec<u8> {
charset_from_flags(true, true, true, true)
}
/// "hex" = 0-9a-f
pub fn charset_hex() -> Vec<u8> {
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<String> {
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<u8> {
charset
.iter()
.copied()
.filter(|c| !AMBIGUOUS_CHARS.contains(c))
.collect()
}