From 3116390ed05f46983dc955f9852d8dbeea041e15 Mon Sep 17 00:00:00 2001 From: Dennis Eriksen Date: Thu, 14 Sep 2023 20:25:53 +0200 Subject: Updated build-instructions for go-version --- README.md | 2 +- go/go.mod | 2 +- go/main.go | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ go/makepass.go | 331 --------------------------------------------------------- 4 files changed, 333 insertions(+), 333 deletions(-) create mode 100644 go/main.go delete mode 100644 go/makepass.go diff --git a/README.md b/README.md index 3b6e34a..d1dc0b4 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ Bash-version. It's a lot messier than the zsh-version. Build width (from root folder in repo): ``` -$ go build -o ../makepass.go -C go/ -ldflags "-s -w" makepass.go +$ go build -C go -o .. -ldflags "-s -w" ``` ### Perl diff --git a/go/go.mod b/go/go.mod index bc5ed12..108f34b 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,4 +1,4 @@ -module makepass +module makepass.go go 1.20 diff --git a/go/main.go b/go/main.go new file mode 100644 index 0000000..a5241a3 --- /dev/null +++ b/go/main.go @@ -0,0 +1,331 @@ +// +// Author : Dennis Eriksen +// File : makepass.go +// Created : 2023-09-05 +// Licence : BSD-3-Clause +// +// Copyright (c) 2018-2023 Dennis Eriksen + +package main + +import ( + "bufio" + "flag" + "fmt" + "golang.org/x/term" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +// Basic constant definitions +const ( + 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 + + lower = "abcdefghijklmnopqrstuvwxyz" + upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + digit = "0123456789" + other = "!$%&#/()=?+-_,.;:<>[]{}|@*" // Special characters for special passwords + + defaultFile = "/usr/share/dict/words" // Default path to the file of words used for passphrases +) + +// Variables for different types of passwords +var ( + alpha []string = strings.Split(lower+upper, "") // Array of alphabets (lowercase and uppercase) + alnum []string = append(alpha, strings.Split(digit, "")...) // Array of alphanumeric characters + every []string = append(alnum, strings.Split(other, "")...) // Array of all characters (alphabet, digit and special) + 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 + 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") + } + + // + // Flag handling + // + 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() + + if help { + printHelp() + } + + // Handle cmd-line args that are not flags + if len(flag.Args()) == 1 { + length, errl = strconv.Atoi(flag.Arg(0)) + } + + // + // Error handling + // + + // We take max one argument + if len(flag.Args()) > 1 { + fmt.Println("only one argument") + os.Exit(1) + } + + // 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) + } + + // + // move on + // + + // initialise the random seed + // TODO: Get seed from /dev/random, like we do in the perl- and zsh-versions? + rand.Seed(time.Now().UnixNano()) + + // get screen width + termWidth, _, err := term.GetSize(0) + if err != nil { + 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 + } + + // number of colums + colNum = termWidth / (colWidth + printLen) + if colNum == 0 { + colNum = 1 + } + + // + // print passwords + // + + // Generate and print normal and special passwords + printColumns("Normal passwords:", number, normal) + + fmt.Println("") + + 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("") + } + } +} + +// Function to generate a random string of given length from given characters +func randstring(chars []string) string { + l := length + if length == 0 { // Random length if not specified + 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 { // 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))]) + } + } + return str.String() +} + +// Function to generate passphrases +func passphrase(words []string) string { + var str strings.Builder + for i := 0; i < passWords; i++ { + 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/go/makepass.go b/go/makepass.go deleted file mode 100644 index a5241a3..0000000 --- a/go/makepass.go +++ /dev/null @@ -1,331 +0,0 @@ -// -// Author : Dennis Eriksen -// File : makepass.go -// Created : 2023-09-05 -// Licence : BSD-3-Clause -// -// Copyright (c) 2018-2023 Dennis Eriksen - -package main - -import ( - "bufio" - "flag" - "fmt" - "golang.org/x/term" - "math/rand" - "os" - "strconv" - "strings" - "time" -) - -// Basic constant definitions -const ( - 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 - - lower = "abcdefghijklmnopqrstuvwxyz" - upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - digit = "0123456789" - other = "!$%&#/()=?+-_,.;:<>[]{}|@*" // Special characters for special passwords - - defaultFile = "/usr/share/dict/words" // Default path to the file of words used for passphrases -) - -// Variables for different types of passwords -var ( - alpha []string = strings.Split(lower+upper, "") // Array of alphabets (lowercase and uppercase) - alnum []string = append(alpha, strings.Split(digit, "")...) // Array of alphanumeric characters - every []string = append(alnum, strings.Split(other, "")...) // Array of all characters (alphabet, digit and special) - 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 - 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") - } - - // - // Flag handling - // - 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() - - if help { - printHelp() - } - - // Handle cmd-line args that are not flags - if len(flag.Args()) == 1 { - length, errl = strconv.Atoi(flag.Arg(0)) - } - - // - // Error handling - // - - // We take max one argument - if len(flag.Args()) > 1 { - fmt.Println("only one argument") - os.Exit(1) - } - - // 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) - } - - // - // move on - // - - // initialise the random seed - // TODO: Get seed from /dev/random, like we do in the perl- and zsh-versions? - rand.Seed(time.Now().UnixNano()) - - // get screen width - termWidth, _, err := term.GetSize(0) - if err != nil { - 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 - } - - // number of colums - colNum = termWidth / (colWidth + printLen) - if colNum == 0 { - colNum = 1 - } - - // - // print passwords - // - - // Generate and print normal and special passwords - printColumns("Normal passwords:", number, normal) - - fmt.Println("") - - 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("") - } - } -} - -// Function to generate a random string of given length from given characters -func randstring(chars []string) string { - l := length - if length == 0 { // Random length if not specified - 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 { // 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))]) - } - } - return str.String() -} - -// Function to generate passphrases -func passphrase(words []string) string { - var str strings.Builder - for i := 0; i < passWords; i++ { - 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) - -} -- cgit v1.2.3