use rand::{RngExt as _, seq::IndexedRandom as _}; const ALPHABET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const CARD_VALUES: &str = "A234567890JQK"; pub fn main() -> Result<(), String> { let mut args = std::env::args(); // Ignore the exe name let _ = args.next(); // Read the first arg as a command or flag match args.next().as_deref() { None => {} Some("--version") => { println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); return Ok(()); } Some(_) => return Err("Unknown CLI arg".to_string()), } println!("{}", create_passphrase(" ", 8)); println!("{}", create_pin(9)); println!("{}", create_base32(8)); // Shuffling all 26 letters allows you to store 88 bits of information on beads on a bracelet. Destroying the bracelet doesn't reveal any information if all 26 letters are used. println!("{}", create_shuffle(ALPHABET)); // Shuffling all 13 values of a suit of playing cards allow you to store 32.5 bits of information. A standard deck of cards can hold 130 bits of entropy ignoring suits. Mixing up the cards provably destroys the information. for _ in 0..4 { println!("{}", create_shuffle(CARD_VALUES)); } // Read a line of input so that the CLI can run in its own terminal without instantly closing the terminal when we exit. println!("Press Enter"); let mut input = String::new(); std::io::stdin().read_line(&mut input).ok(); Ok(()) } /// Generates a Base32 string per RFC 4648, Section 6. /// /// Wikipedia says this is the most popular encoding, and it's what /// `base32` in GNU coreutils uses. /// /// /// This is useful for short IDs, e.g. bugs that aren't living in an issue /// tracker. 8 characters of Base32 is 40 bits of entropy, which is /// not enough for a password pub fn create_base32(len: usize) -> String { let encoding: Vec<_> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".chars().collect(); assert_eq!(encoding.len(), 32); let chars = (0..len).map(|_| *encoding.choose(&mut rand::rng()).unwrap()); String::from_iter(chars) } /// Generates a diceware passphrase pub fn create_passphrase(separator: &str, len: usize) -> String { let dice = Dice::default(); let words: Vec<_> = (0..len).map(|_| dice.pick_word()).collect(); words.join(separator) } /// Generates a decimal PIN /// /// PINs are returned as a String because you do not do math with PINs, therefore they are not numbers. pub fn create_pin(len: usize) -> String { let digits: Vec<_> = (0..len) .map(|_| format!("{}", rand::rng().random_range(0..=9))) .collect(); digits.join("") } pub fn create_shuffle(input: &str) -> String { use rand::prelude::SliceRandom; let mut input: Vec = input.chars().collect(); input.shuffle(&mut rand::rng()); String::from_iter(input.iter()) } pub struct Dice { words: Vec, } impl Default for Dice { fn default() -> Self { let wordlist = include_str!("eff_short_wordlist_1.txt"); let words: Vec<_> = wordlist.split('\n').map(|s| s.to_string()).collect(); assert_eq!(words.len(), 1253); assert_eq!(words[0], "acid"); assert_eq!(words[600], "large"); assert_eq!(words[1252], "zoom"); Self { words } } } impl Dice { pub fn pick_word(&self) -> &str { self.words .choose(&mut rand::rng()) .expect("`choose` should always return `Some`") } }