main.rs (8788B)
1 /* 2 rusty-diceware - a password / passphrasse generator using wordlists, with or without dice. 3 Copyright (C) 2015-2022 Yuval Langer 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as published by 7 the Free Software Foundation, either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public License 16 along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 extern crate getopts; 20 extern crate rand; 21 22 use std::fs::File; 23 use std::io::BufRead; 24 use std::io::Read; 25 use std::process::exit; 26 use std::str::FromStr; 27 28 use diceware_wordlists::Wordlist; 29 use getopts::Options; 30 use rand::prelude::SliceRandom; 31 use rand::rngs::ThreadRng; 32 use rand::thread_rng; 33 34 fn make_options() -> Options { 35 let mut opts = Options::new(); 36 opts.optflag("h", "help", "This help message."); 37 opts.optflag("e", "entropy", "Display number of entropy bits."); 38 opts.optflag("r", "dicerolls", "Provide results of physical dice rolls. Word per line, same digit order as in the files, digits between and including 1 and 6."); 39 opts.optopt("n", "nword", "Number of words in a passphrase.", "NWORD"); 40 opts.optopt( 41 "d", 42 "delimiter", 43 "The delimiter character used to separate the words.", 44 "DELIM", 45 ); 46 opts.optopt("f", "wordlist-file", "Path to a wordlist file.", "FILE"); 47 opts.optopt( 48 "l", 49 "wordlist", 50 "Wordlist to use. (efflong (default), effshort1, effshort2, minilock, reinhold, or beale)", 51 "WORDLIST", 52 ); 53 opts 54 } 55 56 fn print_usage(program: &str, opts: Options) { 57 let brief = format!("Usage: {} [options]", program); 58 print!("{}", opts.usage(&brief)); 59 } 60 61 fn unknown_wordlist(wordlist_name: &str) -> ! { 62 eprintln!( 63 "Unknown wordlist: {}. Available wordlists: efflong (default), effshort1, effshort2, beale, reinhold, or minilock.", 64 wordlist_name, 65 ); 66 exit(-1) 67 } 68 69 fn find_number_of_rolls_needed(wordlist_length: u32) -> u32 { 70 let mut x = wordlist_length as u64; 71 let mut nrolls: u32 = 0; 72 while x != 0 { 73 x /= 6; 74 nrolls += 1; 75 } 76 nrolls 77 } 78 79 fn rolls_to_word_index(number_of_rolls_needed: u32, rolls: &[u8]) -> usize { 80 if number_of_rolls_needed == rolls.len() as u32 { 81 let mut word_number = 0; 82 for (i, roll) in rolls.iter().rev().enumerate() { 83 if *roll < 1 || *roll > 6 { 84 panic!( 85 "Must be a die roll result between and including 1 and 6, not {}", 86 roll 87 ); 88 } 89 word_number += ((roll - 1) as usize) * 6_usize.pow(i as u32); 90 } 91 word_number 92 } else { 93 panic!( 94 "Wrong number of die casts: {}. Needs to be {} die casts.", 95 rolls.len(), 96 number_of_rolls_needed 97 ) 98 } 99 } 100 101 fn print_words_rolls( 102 wordlist: &[&str], 103 delimiter: &char, 104 is_entropy_printed: &bool, 105 rolls: &[&[u8]], 106 ) { 107 let number_of_rolls_needed = find_number_of_rolls_needed(wordlist.len() as u32); 108 for roll_index in 0..(rolls.len() - 1) { 109 let roll_sum = rolls_to_word_index( 110 number_of_rolls_needed, 111 rolls 112 .get(roll_index) 113 .unwrap_or_else(|| panic!("Bad roll index: {}", roll_index)), 114 ); 115 let word = wordlist 116 .get(roll_sum) 117 .unwrap_or_else(|| panic!("Wrong word index: {}", roll_sum)); 118 print!("{}{}", &word, delimiter); 119 } 120 let roll_sum = rolls_to_word_index(number_of_rolls_needed, rolls.last().unwrap()); 121 let word = wordlist.get(roll_sum).unwrap(); 122 print!("{}", &word); 123 124 println!(); 125 if *is_entropy_printed { 126 println!("{}", entropyn(wordlist, rolls.len() as u64)) 127 } 128 } 129 130 fn read_rolls() -> Vec<Vec<u8>> { 131 let stdin = std::io::stdin(); 132 let mut rolls: Vec<Vec<u8>> = Vec::new(); 133 let mut last_number_of_dice = None; 134 for line in stdin.lock().lines() { 135 let line_value = line.unwrap(); 136 let line_value_trimmed = line_value.trim(); 137 if line_value_trimmed.is_empty() { 138 continue; 139 } 140 let current_number_of_dice = line_value_trimmed.len(); 141 if let Some(last_number_of_dice_value) = last_number_of_dice { 142 if last_number_of_dice_value != current_number_of_dice { 143 panic!("Not all dice rolls were of the same number of die."); 144 } else { 145 last_number_of_dice = Some(current_number_of_dice); 146 } 147 } 148 149 rolls.push( 150 line_value_trimmed 151 .chars() 152 .map(|c| { 153 c.to_string() 154 .parse() 155 .unwrap_or_else(|e| panic!("Not a digit: \"{}\". Error: {}", c, e)) 156 }) 157 .collect(), 158 ); 159 } 160 rolls 161 } 162 163 fn entropy(wordlist: &[&str]) -> f64 { 164 (wordlist.len() as f64).log2() 165 } 166 167 fn entropyn(wordlist: &[&str], n: u64) -> f64 { 168 entropy(wordlist) * (n as f64) 169 } 170 171 fn print_words_rng( 172 wordlist: &[&str], 173 word_num: &u64, 174 delimiter: &char, 175 is_entropy_printed: &bool, 176 rng: &mut ThreadRng, 177 ) { 178 for _ in 0..(word_num - 1) { 179 let word = wordlist.choose(rng).unwrap(); 180 print!("{}{}", &word, delimiter); 181 } 182 let word = wordlist.choose(rng).unwrap(); 183 print!("{}", word); 184 185 println!(); 186 if *is_entropy_printed { 187 println!("{}", entropyn(wordlist, *word_num)) 188 } 189 } 190 191 fn load_wordlist_file(filepath: &str) -> String { 192 let mut wordlist_file = match File::open(&filepath) { 193 Ok(ok) => ok, 194 Err(err) => panic!("Unable to open file: {}; due to error: {}", filepath, err), 195 }; 196 let mut wordlist_string = String::new(); 197 if let Err(err) = wordlist_file.read_to_string(&mut wordlist_string) { 198 panic!("Unable to read file: {}; due to error: {}", filepath, err) 199 } 200 wordlist_string 201 } 202 203 fn main() { 204 let args: Vec<String> = std::env::args().collect(); 205 let program = &args[0]; 206 207 let opts = make_options(); 208 209 let matches = match opts.parse(&args[1..]) { 210 Ok(m) => m, 211 Err(f) => { 212 println!("{}\n", f); 213 print_usage(program, opts); 214 exit(-1); 215 } 216 }; 217 218 if matches.opt_present("h") { 219 print_usage(program, opts); 220 return; 221 }; 222 223 let is_physical_rolls = matches.opt_present("r"); 224 225 let word_num: u64 = matches 226 .opt_str("n") 227 .map_or(8, |n_str| n_str.parse::<u64>().ok().unwrap()); 228 229 let delimiter: char = matches 230 .opt_str("d") 231 .map_or(' ', |n_str| n_str.parse::<char>().ok().unwrap()); 232 233 let is_entropy_printed = matches.opt_present("entropy"); 234 235 let wordlist_name = if let Some(wordlist_option) = matches.opt_str("l") { 236 match Wordlist::from_str(&wordlist_option.to_lowercase()) { 237 Ok(list) => list, 238 _ => unknown_wordlist(&wordlist_option), 239 } 240 } else { 241 Wordlist::default() 242 }; 243 244 let mut rng = thread_rng(); 245 246 if let Some(wordlist_filepath) = matches.opt_str("f") { 247 let wordlist_string = load_wordlist_file(&wordlist_filepath); 248 let wordlist = wordlist_string 249 .split('\n') 250 .map(|x| x.trim()) 251 .filter(|x| x != &"") 252 .collect::<Vec<&str>>(); 253 254 if is_physical_rolls { 255 let rolls = read_rolls(); 256 257 print_words_rolls( 258 &wordlist, 259 &delimiter, 260 &is_entropy_printed, 261 &rolls.iter().map(|x| x.as_ref()).collect::<Vec<&[u8]>>(), 262 ); 263 } else if word_num != 0 { 264 print_words_rng( 265 &wordlist, 266 &word_num, 267 &delimiter, 268 &is_entropy_printed, 269 &mut rng, 270 ); 271 }; 272 } else { 273 let wordlist = wordlist_name.get_list(); 274 275 if is_physical_rolls { 276 let rolls = read_rolls(); 277 278 print_words_rolls( 279 wordlist, 280 &delimiter, 281 &is_entropy_printed, 282 &rolls.iter().map(|x| x.as_ref()).collect::<Vec<&[u8]>>(), 283 ); 284 } else if word_num != 0 { 285 print_words_rng( 286 wordlist, 287 &word_num, 288 &delimiter, 289 &is_entropy_printed, 290 &mut rng, 291 ); 292 }; 293 }; 294 }