diff --git a/src/main.rs b/src/main.rs index 6a345f0..4277801 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,19 +2,16 @@ use rand::Rng; use rand::rngs::OsRng; use std::fs; -// enum variant (i keep forgetting the terminology) -// enum PasswordType { -// RandomChars(usize), -// Passphrase(usize, usize), -// } +// alphanumeric only +const CHARSET: &[u8] = + b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; -// non-CTRL chars -fn random_ascii() -> u8 { - // we definitely want to use the OS for randomness - // doesn't require seeding because it is backed by CSPRNG, backed by OS - // the OS maintains it's own entropy from mouse movement, hardware noise,etc - let mut rng = OsRng; +// excludes backtick(`), quote('), dquote("), slash(/), bslash(\) +const CHARSET_WITH_SYMBOLS: &[u8] = + b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()[]{}|?<>,.-_=+~"; + +fn random_ascii(rng: &mut OsRng, char_set: &[u8]) -> u8 { // NOT IDEAL FOR CRYPTO/password generation // let range_max = 122; // 125 - 33 // let n = rng.next_u32() % range_max; // slightly modulo biased @@ -22,47 +19,56 @@ fn random_ascii() -> u8 { // this function under the hood creates a zone that divides evenly // into the range so we don't encounter modulo bias - rng.gen_range(33..125) as u8 + let index = rng.gen_range(0..char_set.len()); + char_set[index] } -fn generate_random_chars(len: usize) -> String { +fn generate_random_chars(len: usize, symbols: bool, rng: &mut OsRng) -> String { // String is a thin wrapper around Vec so we can expect re-allocation // and copy only when we exceed the capacity of the vector - let mut res = String::new(); + // However, with_capacity will perform only one allocation anyway + let mut res = String::with_capacity(len); + let char_set = if symbols { CHARSET_WITH_SYMBOLS } else { CHARSET }; for _ in 0..len { - res.push(random_ascii() as char); + res.push(random_ascii(rng, char_set) as char); } res } -fn generate_passphrase(phrases: usize, len: usize, words: &Vec<&str>) -> String { - let mut res = String::new(); - let mut count = 0; - while count < phrases { - let word = random_word(words); - if word.is_ascii() { // only add ascii words - res.push_str(random_word(words)); - count += 1; - } +// we can take a slice of "words" because we don't care about: +// capacity, mutation (push/pop), allocation strategy +fn generate_passphrase(phrase_count: usize, + word_list: &[&str], rng: &mut OsRng) -> String { + let mut res = String::with_capacity(phrase_count*2); // save a couple allocations + for _ in 0..phrase_count { + res.push_str(random_word(word_list, rng)); } - res.truncate(len); // only works on ascii res } -fn random_word<'a>(words: &'a Vec<&'a str>) -> &'a str { - let mut rng = OsRng; - words[rng.gen_range(0..words.len())] +fn random_word<'a>(word_list: &'a [&'a str], rng: &mut OsRng) -> &'a str { + word_list[rng.gen_range(0..word_list.len())] } fn main() { let content = fs::read_to_string("/usr/share/dict/words") .expect("could not read dictionary"); - let words: Vec<&str> = content.lines().collect(); - let chars = generate_random_chars(20); + // filter words here for only ascii + let word_list: Vec<&str> = content.lines().filter(|w| + w.is_ascii()).collect(); + + // we definitely want to use the OS for randomness + // doesn't require seeding because it is backed by CSPRNG, backed by OS + // the OS maintains it's own entropy from mouse movement, hardware noise,etc + // we pass it through the functions to avoid a potential syscall + let mut rng = OsRng; + let chars = generate_random_chars(20, true, &mut rng); println!("generated random chars: {}", chars.as_str()); - let phrase = generate_passphrase(3, 20, &words); + let phrase = generate_passphrase(3, &word_list, &mut rng); println!("generated passphrase: {}", phrase.as_str()); + + println!("words in dictionary: {}", word_list.len()); }