From f4b37396dee888d9cf6e016d1d59e4c0791035ac Mon Sep 17 00:00:00 2001 From: Dennis Eriksen Date: Mon, 11 Sep 2023 08:26:04 +0200 Subject: Redid Go-version Go-version now matches zsh in functionality also removed rogue whitespace from makepass.zsh --- go/makepass.go | 294 +++++++++++++++++++++++++++++++++++++++++++-------------- makepass.zsh | 2 +- 2 files changed, 224 insertions(+), 72 deletions(-) diff --git a/go/makepass.go b/go/makepass.go index 542b63e..34d7d73 100644 --- a/go/makepass.go +++ b/go/makepass.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "flag" "fmt" "golang.org/x/term" "math/rand" @@ -13,11 +14,7 @@ import ( // Basic constant definitions const ( - normNum = 10 // Number of normal passwords - specNum = 6 // Number of special passwords - passNum = 6 // Number of passphrases - - maxLength = 255 // Maximum length of passwords + max = 255 // Maximum length of passwords rangeMax = 42 // Maximum range for password length rangeMin = 8 // Minimum range for password length passWords = 8 // Number of words in passphrases @@ -38,36 +35,69 @@ var ( normal []string = append(alnum, "-", "_") // Characters for normal passwords special []string = every // Characters for special passwords wordlist string = defaultFile // Path to a dictionary to use for passphrases - length int = 0 // Length of passwords + colWidth int = 1 + colNum int = 1 + + length int = 0 // Length of passwords + number int = 10 + printBool bool = false + printLen int = 0 + help bool = false ) func main() { + + // define error-vars + var errl error // err for length + var errn error // err for number + + // Handle environment + if len(os.Getenv("MAKEPASS_LENGTH")) > 0 { + length, errl = strconv.Atoi(os.Getenv("MAKEPASS_LENGTH")) + } + if len(os.Getenv("MAKEPASS_NUMBER")) > 0 { + number, errn = strconv.Atoi(os.Getenv("MAKEPASS_NUMBER")) + } + // Get wordlist from env + if len(os.Getenv("MAKEPASS_WORDLIST")) > 0 { + wordlist = os.Getenv("MAKEPASS_WORDLIST") + } + // - // Error handling + // Flag handling // - var err error + flag.BoolVar(&help, "h", help, "print helptext") + flag.IntVar(&length, "l", length, "length of passwords to output\nmust be a number between 0 and "+strconv.Itoa(max)) + flag.IntVar(&number, "n", number, "number of passwords to output\nmust be a number between 1 and "+strconv.Itoa(max)) + flag.BoolVar(&printBool, "p", printBool, "print length of each password") + flag.Parse() - // Checking command line argument and environment variable for password length - if len(os.Args) == 2 { - length, err = strconv.Atoi(os.Args[1]) - } else if len(os.Getenv("MAKEPASS_DEFAULT_LENGTH")) > 0 { - length, err = strconv.Atoi(os.Getenv("MAKEPASS_DEFAULT_LENGTH")) + if help { + printHelp() } - // If there is an error in conversion, or if the length is not within limits, exit the program with an error message - if err != nil || (length > maxLength || length < 0) { - fmt.Printf("Error: Argument must be a whole number between 0 and %d.\n", maxLength) - os.Exit(1) + // Handle cmd-line args that are not flags + if len(flag.Args()) == 1 { + length, errl = strconv.Atoi(flag.Arg(0)) } - // Get wordlist from env - if len(os.Getenv("MAKEPASS_WORDLIST")) > 0 { - wordlist = os.Getenv("MAKEPASS_WORDLIST") + // + // Error handling + // + + // We take max one argument + if len(flag.Args()) > 1 { + fmt.Println("only one argument") + os.Exit(1) } - // Check if the wordlist file exists - if _, err := os.Stat(wordlist); os.IsNotExist(err) { - fmt.Println("The file", wordlist, "does not exist") + // If there is an error in conversion, or if the length is not within limits, exit the program with an error message + if errl != nil || (length > max || length < 0) { + fmt.Printf("length must be a number between 0 and %d.\n", max) + os.Exit(1) + } + if errn != nil || (number < 1 || max < number) { + fmt.Printf("number-argument must be between 1 and %d.\n", max) os.Exit(1) } @@ -81,33 +111,91 @@ func main() { // get screen width termWidth, _, err := term.GetSize(0) - - // Read wordlist from file - file, err := os.Open(wordlist) if err != nil { - fmt.Printf("failed to open file: %s", err) + termWidth = 0 + } + + // col width of printLen + if printBool { + // convert length to string and count length of string, and add one for space + // In the other versions we add the space when calculating colNum, but go doesn't support ternary operators. This way we don't need an extra if-statement. Just subtract the space when printing the length in printColumns(). + if length <= 100 { + printLen = 2 + 1 + } else { + printLen = 3 + 1 + } + } else { + printLen = 0 + } + + // column width + colWidth = rangeMax + 2 + if length > 0 { + colWidth = length + 2 } - scanner := bufio.NewScanner(file) - scanner.Split(bufio.ScanLines) - var text []string - for scanner.Scan() { - text = append(text, scanner.Text()) + + // number of colums + colNum = termWidth / (colWidth + printLen) + if colNum == 0 { + colNum = 1 } - file.Close() // // print passwords // // Generate and print normal and special passwords - printColumns("Normal passwords:", 10, normal, termWidth) - fmt.Println("") - printColumns("Passwords with special characters:", 6, special, termWidth) + printColumns("Normal passwords:", number, normal) + fmt.Println("") - // Generate and print passphrases - for i := 0; i < passNum; i++ { - fmt.Println(passphrase(&text)) + printColumns("Passwords with special characters:", number/3*2+1, special) + + // Generate and print passphrases if wordlist exists + if _, err := os.Stat(wordlist); err == nil { + // Read wordlist from file + file, err := os.Open(wordlist) + if err != nil { + fmt.Printf("failed to open file: %s", err) + } + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + var words []string + for scanner.Scan() { + words = append(words, scanner.Text()) + } + file.Close() + + // Print it + fmt.Println("") + fmt.Println("Passphrases:") + + for i := 0; i < number/2; i++ { + fmt.Println(passphrase(words)) + } + } +} + +// Function to generate and print passwords +func printColumns(title string, num int, chars []string) { + var strings []string + + for i := 0; i < num; i++ { + strings = append(strings, randstring(chars)) + } + + // Print title + fmt.Println(title) + + // Print passwords in neat columns + for i, str := range strings { + if printBool { + fmt.Printf("%0[1]*[2]d ", printLen-1, len(str)) + } + fmt.Printf("%-[1]*[2]s", colWidth, str) + if (i+1)%colNum == 0 || (i+1 == num && (i+1)%colNum > 0) { // Add newlines + fmt.Println("") + } } } @@ -115,13 +203,15 @@ func main() { func randstring(chars []string) string { l := length if length == 0 { // Random length if not specified - l = rand.Intn(rangeMax-rangeMin) + rangeMin + l = rand.Intn(rangeMax-rangeMin+1) + rangeMin } var str strings.Builder // Add random characters to str for i := 1; i <= l; i++ { - if i == 1 || i == l { // don't want passwords to start or end with special characters + if i == 1 { // don't want passwords to start or end with special characters + str.WriteString(alnum[rand.Intn(len(alpha))]) + } else if i == l { str.WriteString(alnum[rand.Intn(len(alnum))]) } else { str.WriteString(chars[rand.Intn(len(chars))]) @@ -130,42 +220,104 @@ func randstring(chars []string) string { return str.String() } -// Function to generate and print passwords -func printColumns(title string, num int, chars []string, termWidth int) { - - // column width - colWidth := rangeMax + 2 - if length > 0 { - colWidth = length + 2 - } - - // number of colums - columns := termWidth / colWidth - if columns == 0 { - columns = 1 - } - - // Print title - fmt.Println(title) - - // Print passwords in columns - for i := 1; i <= num; i++ { - fmt.Printf("%-[1]*[2]s", colWidth, randstring(chars)) - if i%columns == 0 || (i == num && i%columns > 0) { // Add newlines - fmt.Println("") - } - } -} - // Function to generate passphrases -func passphrase(arrh *[]string) string { - text := *arrh +func passphrase(words []string) string { var str strings.Builder for i := 0; i < passWords; i++ { - str.WriteString(text[rand.Intn(len(text))]) // Write a random word to the passphrase + str.WriteString(words[rand.Intn(len(words))]) // Write a random word to the passphrase if i != passWords-1 { str.WriteString("-") // Add hyphen between words } } return str.String() } + +// Help-function +func printHelp() { + fmt.Println(`NAME + makepass - create several random passwords + +SYNOPSIS + makepass [OPTIONS] [NUM] + + If a NUM is provided, passwords will be NUM characters long. + + By default ` + "`makepass`" + ` will output passwords from the three following classes: + + - Normal passwords - random strings with letters (both lower and upper + case), numbers, and dashes and underscores. + + - Passwords with special characters - random strings generated from lower + and upper case letters, numbers, and the following characters: + !#$%&/()=?+-_,.;:<>[]{}|@* + + - Passphrases - if we find a dictionary, a series of eight random words + from the dictionary, separated by dashes. The number of words can not be + changed, but you do not have to use all of them. Use as mane as you want. + + The first character will always be alphabetic, and the last will always be + alphanumeric. + +DESCRIPTION + makepass has the following options: + + -h + output this help-text + -l + length of passwords. See MAKEPASS_LENGTH below + -n + number of passwords. See MAKEPASS_NUMBER below + -p + print length of number + +ENVIRONMENT + makepass examines the following environmental variables. + + MAKEPASS_LENGTH + Specifies the length of passwords. Valid values are 0-255. If 0, a + random value between 8 and 42 will be used for each password. -l + overrides this environmental variable, and the argument NUM overrides + that again. So ` + "MAKEPASS_LENGTH=10 makepass -l 12 14" + ` will give + passwords that are 14 characters long, even though both -l and + MAKEPASS_LENGTH also specifies a length. + + MAKEPASS_NUMBER + The number of passwords to generate. This formula is used to determine + how many passwords from each group should be generated: + - (n) normal passwords + - (n / 3 * 2 + 1) special passwords + - (n / 2) passphrases + Where n is 10 by default. Valid values for n are 1-255. Floating-poing + math is not used, so results may vary. + + MAKEPASS_PRINTLEN + If 1, print length of all passwords. If 0, don\'t. + + MAKEPASS_NORMAL + String of characters from which to generate "normal" passwords. + Defaults to: + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ + + MAKEPASS_SPECIAL + String of characters from which to generate passwords with special + characters. Defaults to the same characters as in MAKEPASS_NORMAL, plus + these: + !#$%&/()=?+-_,.;:<>[]{}|@* + + MAKEPASS_WORDLIST + Specifies the dictionary we find words for passphrases in. If this is + unset or empty, we try "/usr/share/dict/words". If that file does not + exist, no passphrases will be provided. + +NOTES + This scripts makes use of $RANDOM - a builtin in zsh which produces a + pseudo-random integer between 0 and 32767, newly generated each time the + parameter is referenced. We initially seed the random number generator with + a random 32bit integer generated from /dev/random. This should provide + enough randomnes to generate sufficiently secure passwords. + +AUTHOR + Dennis Eriksen `) + os.Exit(0) + +} diff --git a/makepass.zsh b/makepass.zsh index 11e6f69..60f2cc1 100755 --- a/makepass.zsh +++ b/makepass.zsh @@ -49,7 +49,7 @@ typeset -gi COL_NUM # Number of columns to print # > The values of RANDOM form an intentionally-repeatable pseudo-random sequence; # > subshells that reference RANDOM will result in identical pseudo-random values # > unless the value of RANDOM is referenced or seeded in the parent shell in -# > between subshell invocations. +# > between subshell invocations. # So remember to throw away a $RANDOM between subshell invocations! # -- cgit v1.2.3