Browse Source

added a first README

Pat Beirne 3 weeks ago
parent
commit
6abbabddc1
3 changed files with 95 additions and 53 deletions
  1. 32 0
      README.md
  2. 6 0
      pwgen/Cargo.toml
  3. 57 53
      pwgen/src/main.rs

+ 32 - 0
README.md

@@ -0,0 +1,32 @@
+# pwgen
+
+Generate word-based passwords. Based on XKCD/936, word-based passwords are easier to remember than a string of random letters/numbers/punctuation.
+
+[TOC]
+
+### Overview
+
+Password generators can be found all over the internet, but most generate random combinations of letters+numbers+punctuation. If a combination of those is long enough to be secure, it is also long enough to be difficult to remember, and possible difficult to type.
+
+The XKCD comic #936 introduced the idea that a phrase of 3, 4 or 5 common words can be used as a password that is sufficiently difficult to crack in our lifetimes. The program **pwgen** is a locally hosted generator of these kinds of pass phrases.
+
+The program runs locally on a UNIX/Linux based computer, and the passphrase never crosses the network, so you can be sure that you're the only one who has ever seen it.
+
+### Usage
+
+
+
+
+
+
+#### Other implementations
+
+http://www.egansoft.com/password/index.php
+
+https://passwords-generator.org/words
+
+https://passwordcreator.org/commonwords.html
+
+https://mdigi.tools/memorable-password/
+
+https://www.mapletech.co.uk/tools/password-generator/ 

+ 6 - 0
pwgen/Cargo.toml

@@ -7,3 +7,9 @@ edition = "2021"
 
 [dependencies]
 rand = "0.8.4"
+
+[profile.release]
+strip = true
+opt-level = "z"
+lto = true
+

+ 57 - 53
pwgen/src/main.rs

@@ -7,6 +7,7 @@ usage: pwgen -c [number of phrases, default=1]
     
 */
 use std::env;
+use std::cmp;
 use std::fs::File;
 use std::io::{BufReader, BufRead};
 use rand::Rng;
@@ -14,7 +15,7 @@ use rand::Rng;
 #[derive(Debug)]
 struct Settings {
   target_len: usize,
-  target_len_set: bool,
+  collecting_parameter: bool,
   use_conjunction: bool,
   number: u16,
   help: bool,
@@ -23,23 +24,21 @@ struct Settings {
 }
 
 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",
    "for", "for a", "for the", "but", "so"];
 
-
-
 fn main() -> std::io::Result<()> {
   let mut set = Settings {
     number: 1, 
-    target_len: 20, target_len_set: false, 
+    target_len: 20, 
+    collecting_parameter: false, 
     use_conjunction: false, 
-    // debug: false,
     help: false, 
-		// owasp.org/www-community/password-special-characters
+      // owasp.org/www-community/password-special-characters
     pad_chars: " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".to_string() 
-		// princeton IT
-	 //pad_chars: " ~`!@#$%^&*()-_+={}[]|\\;:\"<>,./?".to_string()
+      // princeton IT
+    //pad_chars: " ~`!@#$%^&*()-_+={}[]|\\;:\"<>,./?".to_string()
   };
 
   let args: Vec<String> = env::args().collect();
@@ -48,50 +47,55 @@ fn main() -> std::io::Result<()> {
     usage();
     return Ok(())
   }
-  
+    
   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 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 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(())
 }
 
 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]
        pygen -c [num_phrases]
        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) {
   // dbg!(a);
   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(),
       ("-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,
       ("-h", ..) | ("--help", ..) => s.help = true,
       (.., Ok(x)) => s.number = x,
       _ => 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);
 }