apple_password_gen/
lib.rs

1use rand::{Rng, TryRngCore};
2use rand_core::OsRng;
3
4const VOWELS: &'static [u8] = b"aeiouy";
5// no `l`
6const CONSONANTS: &'static [u8] = b"bcdfghjkmnpqrstvwxz";
7
8/// Generate a random password in the form of `cvccvc-cvccvc-cvccvc`, with 1
9/// capital and 1 number replacing a letter. No `L` is used in the password.
10///
11/// The resulting password is always ascii 20 characters long.
12///
13/// The format matches the passwords generated by Apple's Strong Password
14/// Suggestion, except we don't include filtering for "bad" words. If you have a
15/// "bad" words matcher, call this function repeatedly until no bad words are
16/// found.
17#[must_use]
18pub fn generate() -> String {
19    generate_with_rng(OsRng.unwrap_err())
20}
21
22/// Generate a random password in the form of `cvccvc-cvccvc-cvccvc`, with 1
23/// capital and 1 number replacing a letter. Use a sepecific Rng to do so.
24///
25/// See [`generate`] for more information.
26///
27/// Be sure to pick a secure rng. `rand_core::OsRng`, for example.
28#[must_use]
29pub fn generate_with_rng<T: rand::CryptoRng + Rng>(mut rng: T) -> String {
30    assert_eq!(VOWELS.len(), 6);
31    assert_eq!(CONSONANTS.len(), 19);
32
33    // vessIK-dowbec-ferzi6
34    // govwun-disMad-8wasde
35    // seppem-nothis-kopbI4
36    // zogxuf-xubFat-kyassa8
37    // vabTog-ciwfig-7zunfy
38    // kijzy4-cijhiz-pecguG
39    // mupvaz-1qyzru-vAxfex
40    // gYcmi4-misbyh-zobpin
41    // zertun-togba2-kijruH
42    let pattern_cv = b"cvccvc-cvccvc-cvccvc";
43    let pattern_nu = b".....1.1....1.1....1";
44
45    let number_pos_ct = pattern_nu
46        .iter()
47        .fold(0usize, |acc, &c| if c == b'1' { acc + 1 } else { acc });
48
49    let letter_pos_ct =
50        pattern_cv.iter().fold(
51            0usize,
52            |acc, &c| if c == b'c' || c == b'v' { acc + 1 } else { acc },
53        ) - 1;
54
55    // pick number position. The number replaces a potential letter
56    let mut number_i = rng.random_range(0..number_pos_ct);
57    let mut number_pos = None;
58    for (i, p) in pattern_nu.iter().enumerate() {
59        if *p == b'1' {
60            if number_i == 0 {
61                number_pos = Some(i);
62                break;
63            }
64            number_i -= 1;
65        }
66    }
67    let number_pos = number_pos.unwrap();
68
69    // pick upper-case position. This modifies a chosen letter
70    let uppercase_pos = rng.random_range(0..letter_pos_ct);
71
72    let mut output = String::with_capacity(pattern_cv.len());
73
74    // fill in letters
75    let mut letter_pos = 0;
76    for (i, p) in pattern_cv.iter().enumerate() {
77        if i == number_pos {
78            output.push_str(&format!("{}", rng.random_range(0..10)));
79        } else {
80            match *p {
81                b'c' => {
82                    if letter_pos == uppercase_pos {
83                        output.push(
84                            CONSONANTS[rng.random_range(0..CONSONANTS.len())].to_ascii_uppercase()
85                                as char,
86                        );
87                    } else {
88                        output.push(CONSONANTS[rng.random_range(0..CONSONANTS.len())] as char);
89                    }
90                    letter_pos += 1;
91                }
92                b'v' => {
93                    if letter_pos == uppercase_pos {
94                        output.push(
95                            VOWELS[rng.random_range(0..VOWELS.len())].to_ascii_uppercase() as char,
96                        );
97                    } else {
98                        output.push(VOWELS[rng.random_range(0..VOWELS.len())] as char);
99                    }
100                    letter_pos += 1;
101                }
102                _ => {
103                    output.push(*p as char);
104                }
105            }
106        }
107    }
108
109    output
110}