rusty-diceware

Commandline diceware, with or without dice, written in Rustlang.
git clone https://kaka.farm/~git/rusty-diceware
Log | Files | Refs | README | LICENSE

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 }