109 lines
3.2 KiB
Rust
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()
|
|
}
|