|
@@ -2,43 +2,69 @@
|
|
|
|
|
|
generate passwords in useful ways
|
|
generate passwords in useful ways
|
|
|
|
|
|
-usage: pwgen -c [number of phrases, default=1]
|
|
|
|
- pwgen [len default=20] [number of phrases, default=1] //no conjunctions
|
|
|
|
|
|
+usage: pwgen [options] [number of phrases, default=1]
|
|
|
|
|
|
*/
|
|
*/
|
|
use std::env;
|
|
use std::env;
|
|
-use std::cmp;
|
|
|
|
use std::fs::File;
|
|
use std::fs::File;
|
|
use std::io::{BufReader, BufRead};
|
|
use std::io::{BufReader, BufRead};
|
|
use rand::Rng;
|
|
use rand::Rng;
|
|
|
|
|
|
|
|
+#[derive(Debug)]
|
|
|
|
+#[derive(PartialEq)]
|
|
|
|
+enum Padding {
|
|
|
|
+ Camel, Snake,
|
|
|
|
+ Dots, Spaces,
|
|
|
|
+ Punct, Numbers
|
|
|
|
+}
|
|
|
|
+
|
|
#[derive(Debug)]
|
|
#[derive(Debug)]
|
|
struct Settings {
|
|
struct Settings {
|
|
|
|
+ first_punct: bool, last_punct: bool,
|
|
|
|
+ first_number: bool, last_number: bool,
|
|
|
|
+ capitalize: bool,
|
|
|
|
+ use_conjunctions: bool,
|
|
target_len: usize,
|
|
target_len: usize,
|
|
collecting_parameter: bool,
|
|
collecting_parameter: bool,
|
|
- use_conjunction: bool,
|
|
|
|
number: u16,
|
|
number: u16,
|
|
|
|
+ padding: Padding,
|
|
help: bool,
|
|
help: bool,
|
|
- pad_chars: String,
|
|
|
|
- // debug: bool,
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-const CONJUNCTIONS: &[&str] = &["and", "and a", "and the","or",
|
|
|
|
|
|
+const DOTS: &str = ".,";
|
|
|
|
+const PUNCTS: &str = " !@#$%^&*()-_=+[{]}\\|;:/?.>,<~";
|
|
|
|
+const NUMBERS: &str = "0123456789";
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+const CONJUNCTIONS : &[&str] = &["and", "and a", "and the","or",
|
|
"or the", "or a", "with a", "with", "with the", "by a", "by the",
|
|
"or the", "or a", "with a", "with", "with the", "by a", "by the",
|
|
"on a", "on the","on", "in a", "in the", "in",
|
|
"on a", "on the","on", "in a", "in the", "in",
|
|
"for", "for a", "for the", "but", "so"];
|
|
"for", "for a", "for the", "but", "so"];
|
|
|
|
|
|
|
|
+
|
|
|
|
+fn get_word(set:&Settings, words:&Vec<String>) -> String {
|
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
|
+ let m = &words[rng.gen_range(0 .. words.len())];
|
|
|
|
+ if set.capitalize || set.padding==Padding::Camel {
|
|
|
|
+ // m = m.capitalize(); from https://stackoverflow.com/questions/38406793/
|
|
|
|
+ let mut c = m.chars();
|
|
|
|
+ match c.next() {
|
|
|
|
+ None => String::new(),
|
|
|
|
+ Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ m.to_string()
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
fn main() -> std::io::Result<()> {
|
|
fn main() -> std::io::Result<()> {
|
|
let mut set = Settings {
|
|
let mut set = Settings {
|
|
- number: 1,
|
|
|
|
- target_len: 20,
|
|
|
|
|
|
+ first_punct: false, last_punct: false, first_number: false, last_number: false,
|
|
|
|
+ capitalize: false, use_conjunctions: false,
|
|
|
|
+ number: 1, target_len: 20,
|
|
collecting_parameter: false,
|
|
collecting_parameter: false,
|
|
- use_conjunction: false,
|
|
|
|
help: false,
|
|
help: false,
|
|
- // owasp.org/www-community/password-special-characters
|
|
|
|
- pad_chars: " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".to_string()
|
|
|
|
- // princeton IT
|
|
|
|
- //pad_chars: " ~`!@#$%^&*()-_+={}[]|\\;:\"<>,./?".to_string()
|
|
|
|
|
|
+ padding: Padding::Spaces,
|
|
};
|
|
};
|
|
|
|
|
|
let args: Vec<String> = env::args().collect();
|
|
let args: Vec<String> = env::args().collect();
|
|
@@ -55,78 +81,117 @@ fn main() -> std::io::Result<()> {
|
|
.map(|x| x.expect("bad line"))
|
|
.map(|x| x.expect("bad line"))
|
|
.filter(|x| !x.ends_with("'s"))
|
|
.filter(|x| !x.ends_with("'s"))
|
|
.collect();
|
|
.collect();
|
|
- let num_words = words.len();
|
|
|
|
|
|
|
|
let mut rng = rand::thread_rng();
|
|
let mut rng = rand::thread_rng();
|
|
for _i in 0 .. set.number {
|
|
for _i in 0 .. set.number {
|
|
- let w0 = &words[rng.gen_range(0 .. num_words)];
|
|
|
|
- let w1 = &words[rng.gen_range(0 .. num_words)];
|
|
|
|
- let w2 = &words[rng.gen_range(0 .. num_words)];
|
|
|
|
- let mut words_len = w0.len() + w1.len();
|
|
|
|
-
|
|
|
|
- let padding = set.pad_chars.chars()
|
|
|
|
- .nth(rng.gen_range(0 .. set.pad_chars.len()))
|
|
|
|
- .unwrap()
|
|
|
|
- .to_string();
|
|
|
|
-
|
|
|
|
- if set.use_conjunction {
|
|
|
|
- let mut conj = CONJUNCTIONS[rng.gen_range(0 .. CONJUNCTIONS.len())].to_string();
|
|
|
|
- if conj.ends_with(" a") && w1.starts_with(|x| "aAeEiIoOuU".contains(x)) {
|
|
|
|
- conj.push('n');
|
|
|
|
|
|
+ let mut w = Vec::from([get_word(&set, &words)]);
|
|
|
|
+ let num_extra_chars = if set.first_punct {1} else {0}
|
|
|
|
+ + if set.last_punct {1} else {0}
|
|
|
|
+ + if set.first_number {1} else {0}
|
|
|
|
+ + if set.last_number {1} else {0};
|
|
|
|
+ while w.iter().map(|a| a.len()).sum::<usize>()
|
|
|
|
+ + if set.padding==Padding::Camel {0} else {w.len()-1}
|
|
|
|
+ + num_extra_chars < set.target_len {
|
|
|
|
+ if set.use_conjunctions {
|
|
|
|
+ let c = CONJUNCTIONS[rng.gen_range(0..CONJUNCTIONS.len())];
|
|
|
|
+ w.extend_from_slice(c.split_whitespace()
|
|
|
|
+ .map(|x| x.to_string())
|
|
|
|
+ .collect::<Vec<_>>()
|
|
|
|
+ .as_ref());
|
|
}
|
|
}
|
|
- println!("{} {} {}", w0, conj, w1);
|
|
|
|
- } else {
|
|
|
|
- let pw;
|
|
|
|
- let r:usize;
|
|
|
|
- // if 2 words is enough
|
|
|
|
- if words_len+3 >= set.target_len {
|
|
|
|
- r = cmp::max(set.target_len, words_len+1) - words_len;
|
|
|
|
- pw = format!("{}{}{}",w0,padding.repeat(r),w1);
|
|
|
|
- } else {
|
|
|
|
- // we need 3 words
|
|
|
|
- words_len = words_len + w2.len();
|
|
|
|
- r = cmp::max(set.target_len, words_len+2) - words_len;
|
|
|
|
- pw = format!("{}{}{}{}{}",w0,padding.repeat(r/2),w1,padding.repeat(r-r/2),w2);
|
|
|
|
|
|
+
|
|
|
|
+ w.push(get_word(&set, &words));
|
|
|
|
+
|
|
|
|
+ let w_len = w.len();
|
|
|
|
+ if w[w_len-2] == "a" && "aeiouAEIOU".find(w[w_len-1].chars().nth(0).unwrap()).is_some() {
|
|
|
|
+ w[w_len - 2] = "an".to_string();
|
|
}
|
|
}
|
|
- println!("{}",pw);
|
|
|
|
}
|
|
}
|
|
|
|
+ let mut ret : String;
|
|
|
|
+ match set.padding {
|
|
|
|
+ Padding::Camel => ret = w.join(""),
|
|
|
|
+ Padding::Spaces => ret = w.join(" "),
|
|
|
|
+ Padding::Dots => ret = w.join(&DOTS.chars().nth(rng.gen_range(0 .. DOTS.len())).unwrap().to_string()),
|
|
|
|
+ Padding::Punct => ret = w.join(&PUNCTS.chars().nth(rng.gen_range(0 .. PUNCTS.len())).unwrap().to_string()),
|
|
|
|
+ Padding::Numbers => ret = w.join(&NUMBERS.chars().nth(rng.gen_range(0 .. NUMBERS.len())).unwrap().to_string()),
|
|
|
|
+ Padding::Snake => ret = w.join("_"),
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let n = NUMBERS.chars().nth(rng.gen_range(0 .. NUMBERS.len())).unwrap().to_string();
|
|
|
|
+ if set.first_number {
|
|
|
|
+ ret = n.clone() + &ret;
|
|
|
|
+ }
|
|
|
|
+ if set.last_number {
|
|
|
|
+ ret = ret + &n;
|
|
|
|
+ }
|
|
|
|
+ let p = PUNCTS.chars().nth(rng.gen_range(0 .. PUNCTS.len())).unwrap().to_string();
|
|
|
|
+ if set.first_punct {
|
|
|
|
+ ret = p.clone() + &ret;
|
|
|
|
+ }
|
|
|
|
+ if set.last_punct {
|
|
|
|
+ ret = ret + &p;
|
|
|
|
+ }
|
|
|
|
+ println!("{}",ret);
|
|
}
|
|
}
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
fn usage() {
|
|
fn usage() {
|
|
- println!("pygen v1.1 generate passphrase; inspired by xkcd/936
|
|
|
|
-Usage: pygen [-p | -P | -n | -N] [-L <n> | --length <n>] [num_phrases]
|
|
|
|
- pygen -c [num_phrases]
|
|
|
|
- pygen -h | --help
|
|
|
|
- -p pad with only spaces (for smartphone)
|
|
|
|
- -P pad with spaces, period, comma (for smartphone)
|
|
|
|
- -n pad with numbers
|
|
|
|
- -N pad with numbers, spaces, periods, comma
|
|
|
|
- -L --length add extra filler to reach length (default=20)
|
|
|
|
- -c make 2 word phrases with a conjunction filler
|
|
|
|
- num_phrases make multiple passphrases (default=1)");
|
|
|
|
|
|
+ println!("pygen v1.4 generate passphrase; inspired by xkcd/936
|
|
|
|
+Usage: pwgen.py [-sSpPkKnNcCaMX] [-L length] [num_phrases]
|
|
|
|
+ pwgen.py -h | --help | --version
|
|
|
|
|
|
|
|
+ -s pad with only spaces (default; for smartphone)
|
|
|
|
+ -S pad with period and comma (for smartphone)
|
|
|
|
+ -p pad with special characters
|
|
|
|
+ -P pad with numbers
|
|
|
|
+ -k connect the words as CamelCase
|
|
|
|
+ -K connect the words as snake_case
|
|
|
|
+ -n add a number at the beginning
|
|
|
|
+ -N add a number at the end
|
|
|
|
+ -c add a special character at the begnning
|
|
|
|
+ -C add a special character at the end
|
|
|
|
+ -M capitalize the words
|
|
|
|
+ -a connect the words with a conjuction filler
|
|
|
|
+ -X same as -MNCS (capital, periods & commas, extra number & char)
|
|
|
|
+ -L <n> make pass phrases at least this long; default=20
|
|
|
|
+ num_phrases make multiple pass phrases, one per line; default=1\n");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_args(a:&Vec<String>, s:&mut Settings) {
|
|
fn parse_args(a:&Vec<String>, s:&mut Settings) {
|
|
// dbg!(a);
|
|
// dbg!(a);
|
|
for item in &a[1 .. ] {
|
|
for item in &a[1 .. ] {
|
|
-
|
|
|
|
if s.collecting_parameter == false {
|
|
if s.collecting_parameter == false {
|
|
- match (item.as_str(),item.parse::<u16>()) {
|
|
|
|
- ("-p", ..) => s.pad_chars = " ".to_string(),
|
|
|
|
- ("-P", ..) => s.pad_chars = " ,.".to_string(),
|
|
|
|
- ("-n", ..) => s.pad_chars = "0123456789".to_string(),
|
|
|
|
- ("-N", ..) => s.pad_chars = " ,.0123456789".to_string(),
|
|
|
|
- ("-L", ..) | ("--length", ..) => s.collecting_parameter = true,
|
|
|
|
- ("-c", ..) => s.use_conjunction = true,
|
|
|
|
- ("-h", ..) | ("--help", ..) => s.help = true,
|
|
|
|
- (.., Ok(x)) => s.number = x,
|
|
|
|
- _ => println!("invalid option: {}", item),
|
|
|
|
|
|
+ let mut chr_itr = item.as_str().chars(); // pick off the first char
|
|
|
|
+ if chr_itr.next() == Some('-') {
|
|
|
|
+ for b in chr_itr { // TODO: skip the first, we've already digested it
|
|
|
|
+ // println!("got a char {}", b);
|
|
|
|
+ match b {
|
|
|
|
+ 'h' => s.help = true,
|
|
|
|
+ 's' => s.padding = Padding::Spaces,
|
|
|
|
+ 'S' => s.padding = Padding::Dots,
|
|
|
|
+ 'p' => s.padding = Padding::Punct,
|
|
|
|
+ 'P' => s.padding = Padding::Numbers,
|
|
|
|
+ 'k' => s.padding = Padding::Camel,
|
|
|
|
+ 'K' => s.padding = Padding::Snake,
|
|
|
|
+ 'c' => s.first_punct = true,
|
|
|
|
+ 'C' => s.last_punct = true,
|
|
|
|
+ 'n' => s.first_number = true,
|
|
|
|
+ 'N' => s.last_number = true,
|
|
|
|
+ 'X' => {s.capitalize = true; s.padding = Padding::Dots;
|
|
|
|
+ s.last_punct = true; s.last_number = true; },
|
|
|
|
+ 'M' => s.capitalize = true,
|
|
|
|
+ 'a' => s.use_conjunctions = true,
|
|
|
|
+ 'L' => s.collecting_parameter = true,
|
|
|
|
+ _ => println!("unexpected command option: {}", b),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else { // not a hyphen-option phrase, check for an int
|
|
|
|
+ //println!("got a command line option {}",item.to_string());
|
|
|
|
+ s.number = item.parse::<u16>().expect("phrase count must be a positive integer");
|
|
}
|
|
}
|
|
- } else {
|
|
|
|
|
|
+ } else { // pick up the value of the -L argument
|
|
s.collecting_parameter = false;
|
|
s.collecting_parameter = false;
|
|
s.target_len = item.parse::<usize>().expect("length must be a positive integer");
|
|
s.target_len = item.parse::<usize>().expect("length must be a positive integer");
|
|
}
|
|
}
|