|
@@ -7,6 +7,7 @@ usage: pwgen -c [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;
|
|
@@ -14,7 +15,7 @@ use rand::Rng;
|
|
#[derive(Debug)]
|
|
#[derive(Debug)]
|
|
struct Settings {
|
|
struct Settings {
|
|
target_len: usize,
|
|
target_len: usize,
|
|
- target_len_set: bool,
|
|
|
|
|
|
+ collecting_parameter: bool,
|
|
use_conjunction: bool,
|
|
use_conjunction: bool,
|
|
number: u16,
|
|
number: u16,
|
|
help: bool,
|
|
help: bool,
|
|
@@ -23,23 +24,21 @@ struct Settings {
|
|
}
|
|
}
|
|
|
|
|
|
const CONJUNCTIONS: &[&str] = &["and", "and a", "and the","or",
|
|
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 main() -> std::io::Result<()> {
|
|
fn main() -> std::io::Result<()> {
|
|
let mut set = Settings {
|
|
let mut set = Settings {
|
|
number: 1,
|
|
number: 1,
|
|
- target_len: 20, target_len_set: false,
|
|
|
|
|
|
+ target_len: 20,
|
|
|
|
+ collecting_parameter: false,
|
|
use_conjunction: false,
|
|
use_conjunction: false,
|
|
- // debug: false,
|
|
|
|
help: false,
|
|
help: false,
|
|
- // owasp.org/www-community/password-special-characters
|
|
|
|
|
|
+ // owasp.org/www-community/password-special-characters
|
|
pad_chars: " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".to_string()
|
|
pad_chars: " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".to_string()
|
|
- // princeton IT
|
|
|
|
- //pad_chars: " ~`!@#$%^&*()-_+={}[]|\\;:\"<>,./?".to_string()
|
|
|
|
|
|
+ // princeton IT
|
|
|
|
+ //pad_chars: " ~`!@#$%^&*()-_+={}[]|\\;:\"<>,./?".to_string()
|
|
};
|
|
};
|
|
|
|
|
|
let args: Vec<String> = env::args().collect();
|
|
let args: Vec<String> = env::args().collect();
|
|
@@ -48,50 +47,55 @@ fn main() -> std::io::Result<()> {
|
|
usage();
|
|
usage();
|
|
return Ok(())
|
|
return Ok(())
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
let file = File::open("/usr/share/dict/words")
|
|
let file = File::open("/usr/share/dict/words")
|
|
- .expect("Dictionary file /usr/share/dict/words not found");
|
|
|
|
|
|
+ .expect("Dictionary file /usr/share/dict/words not found");
|
|
let buf = BufReader::new(file);
|
|
let buf = BufReader::new(file);
|
|
- let words: Vec<String> = buf.lines().map(|x| x.expect("bad line")).collect();
|
|
|
|
|
|
+ let words: Vec<String> = buf.lines()
|
|
|
|
+ .map(|x| x.expect("bad line"))
|
|
|
|
+ .filter(|x| !x.ends_with("'s"))
|
|
|
|
+ .collect();
|
|
let num_words = words.len();
|
|
let num_words = words.len();
|
|
-
|
|
|
|
- let mut rng = rand::thread_rng();
|
|
|
|
-
|
|
|
|
- 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 w3 = &words[rng.gen_range(0 .. num_words)];
|
|
|
|
-
|
|
|
|
- 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".find(x).is_some()) {
|
|
|
|
- conj.push('n');
|
|
|
|
- }
|
|
|
|
- println!("{} {} {}", w0, conj, w1);
|
|
|
|
-
|
|
|
|
- } else {
|
|
|
|
-
|
|
|
|
- let mut pw = format!("{}{}{}",w0,padding,w1);
|
|
|
|
- if pw.len() < set.target_len {
|
|
|
|
- pw = format!("{}{}{}",pw,padding,w2);
|
|
|
|
- if pw.len() < set.target_len {
|
|
|
|
- pw = format!("{}{}{}",pw,padding,w3);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- println!("{}",pw);
|
|
|
|
- }
|
|
|
|
|
|
+
|
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
|
+ 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');
|
|
|
|
+ }
|
|
|
|
+ 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);
|
|
|
|
+ }
|
|
|
|
+ println!("{}",pw);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
fn usage() {
|
|
fn usage() {
|
|
- println!("pygen v1.0 generate passphrase; inspired by xkcd/936
|
|
|
|
|
|
+ println!("pygen v1.1 generate passphrase; inspired by xkcd/936
|
|
Usage: pygen [-p | -P | -n | -N] [-L <n> | --length <n>] [num_phrases]
|
|
Usage: pygen [-p | -P | -n | -N] [-L <n> | --length <n>] [num_phrases]
|
|
pygen -c [num_phrases]
|
|
pygen -c [num_phrases]
|
|
pygen -h | --help
|
|
pygen -h | --help
|
|
@@ -109,23 +113,23 @@ Usage: pygen [-p | -P | -n | -N] [-L <n> | --length <n>] [num_phrases]
|
|
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.target_len_set {
|
|
|
|
- s.target_len_set = true;
|
|
|
|
- s.target_len = item.parse::<usize>().expect("length must be a positive integer");
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- match (item.as_str(),item.parse::<u16>()) {
|
|
|
|
|
|
+ if s.collecting_parameter == false {
|
|
|
|
+ match (item.as_str(),item.parse::<u16>()) {
|
|
("-p", ..) => s.pad_chars = " ".to_string(),
|
|
("-p", ..) => s.pad_chars = " ".to_string(),
|
|
("-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(),
|
|
("-N", ..) => s.pad_chars = " ,.0123456789".to_string(),
|
|
("-N", ..) => s.pad_chars = " ,.0123456789".to_string(),
|
|
- ("-L", ..) | ("--length", ..) => s.target_len_set = true,
|
|
|
|
|
|
+ ("-L", ..) | ("--length", ..) => s.collecting_parameter = true,
|
|
("-c", ..) => s.use_conjunction = true,
|
|
("-c", ..) => s.use_conjunction = true,
|
|
("-h", ..) | ("--help", ..) => s.help = true,
|
|
("-h", ..) | ("--help", ..) => s.help = true,
|
|
(.., Ok(x)) => s.number = x,
|
|
(.., Ok(x)) => s.number = x,
|
|
_ => println!("invalid option: {}", item),
|
|
_ => println!("invalid option: {}", item),
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ s.collecting_parameter = false;
|
|
|
|
+ s.target_len = item.parse::<usize>().expect("length must be a positive integer");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- //dbg!(s);
|
|
|
|
|
|
+ // dbg!(s);
|
|
}
|
|
}
|