diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | README.md | 58 | ||||
-rw-r--r-- | go/go.mod | 7 | ||||
-rw-r--r-- | go/go.sum | 4 | ||||
-rw-r--r-- | go/makepass.go | 171 |
5 files changed, 227 insertions, 16 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e720fd5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# ignore binaries + +/makepass.go @@ -82,26 +82,52 @@ nice job. Here are the results so far: ``` -% hyperfine --time-unit=millisecond --warmup=1 --shell=none ./*(.) +% hyperfine --time-unit=millisecond --warmup=1 --shell=none ./makepass.* Benchmark 1: ./makepass.bash - Time (mean ± σ): 469.3 ms ± 3.6 ms [User: 92.0 ms, System: 734.0 ms] - Range (min … max): 463.6 ms … 474.3 ms 10 runs + Time (mean ± σ): 422.7 ms ± 3.1 ms [User: 95.0 ms, System: 659.0 ms] + Range (min … max): 417.9 ms … 428.3 ms 10 runs -Benchmark 2: ./makepass.pl - Time (mean ± σ): 38.8 ms ± 0.4 ms [User: 18.2 ms, System: 19.0 ms] - Range (min … max): 38.1 ms … 40.1 ms 77 runs +Benchmark 2: ./makepass.go + Time (mean ± σ): 6.9 ms ± 0.2 ms [User: 1.1 ms, System: 4.7 ms] + Range (min … max): 6.3 ms … 8.1 ms 434 runs -Benchmark 3: ./makepass.sh - Time (mean ± σ): 1261.6 ms ± 18.5 ms [User: 298.0 ms, System: 2030.0 ms] - Range (min … max): 1237.4 ms … 1291.8 ms 10 runs +Benchmark 3: ./makepass.pl + Time (mean ± σ): 36.2 ms ± 0.4 ms [User: 17.8 ms, System: 16.2 ms] + Range (min … max): 35.6 ms … 37.9 ms 82 runs -Benchmark 4: ./makepass.zsh - Time (mean ± σ): 27.2 ms ± 0.7 ms [User: 12.0 ms, System: 13.0 ms] - Range (min … max): 25.5 ms … 29.1 ms 111 runs +Benchmark 4: ./makepass.sh + Time (mean ± σ): 1107.7 ms ± 15.3 ms [User: 276.0 ms, System: 1790.0 ms] + Range (min … max): 1084.9 ms … 1140.3 ms 10 runs + +Benchmark 5: ./makepass.zsh + Time (mean ± σ): 25.3 ms ± 0.7 ms [User: 12.7 ms, System: 10.8 ms] + Range (min … max): 23.6 ms … 27.7 ms 114 runs Summary - './makepass.zsh' ran - 1.43 ± 0.04 times faster than './makepass.pl' - 17.27 ± 0.49 times faster than './makepass.bash' - 46.43 ± 1.43 times faster than './makepass.sh' + './makepass.go' ran + 3.69 ± 0.15 times faster than './makepass.zsh' + 5.28 ± 0.17 times faster than './makepass.pl' + 61.62 ± 1.86 times faster than './makepass.bash' + 161.48 ± 5.22 times faster than './makepass.sh' +``` + +## Versions + +### Bash +This version needs a bit more work. + +### Go +Build width (from root folder in repo): ``` +$ go build -o ../makepass.go -C go/ -ldflags "-s -w" makepass.go +``` + +### Perl +Perl version. I like this version. + +### Shell +Needs more work. + +### Zsh +This is currently the "main" version. It uses pure zsh, with no forking out to +other programs. As of adding the go-version, it is the second fastest version. diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 0000000..bc5ed12 --- /dev/null +++ b/go/go.mod @@ -0,0 +1,7 @@ +module makepass + +go 1.20 + +require golang.org/x/term v0.12.0 + +require golang.org/x/sys v0.12.0 // indirect diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 0000000..8c01403 --- /dev/null +++ b/go/go.sum @@ -0,0 +1,4 @@ +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= diff --git a/go/makepass.go b/go/makepass.go new file mode 100644 index 0000000..542b63e --- /dev/null +++ b/go/makepass.go @@ -0,0 +1,171 @@ +package main + +import ( + "bufio" + "fmt" + "golang.org/x/term" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +// 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 + 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 + length int = 0 // Length of passwords +) + +func main() { + // + // Error handling + // + var err error + + // 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 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) + } + + // Get wordlist from env + if len(os.Getenv("MAKEPASS_WORDLIST")) > 0 { + wordlist = os.Getenv("MAKEPASS_WORDLIST") + } + + // Check if the wordlist file exists + if _, err := os.Stat(wordlist); os.IsNotExist(err) { + fmt.Println("The file", wordlist, "does not exist") + 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) + + // 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 text []string + for scanner.Scan() { + text = append(text, scanner.Text()) + } + 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) + fmt.Println("") + + // Generate and print passphrases + for i := 0; i < passNum; i++ { + fmt.Println(passphrase(&text)) + } +} + +// 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) + 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 + str.WriteString(alnum[rand.Intn(len(alnum))]) + } else { + str.WriteString(chars[rand.Intn(len(chars))]) + } + } + 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 + var str strings.Builder + for i := 0; i < passWords; i++ { + str.WriteString(text[rand.Intn(len(text))]) // Write a random word to the passphrase + if i != passWords-1 { + str.WriteString("-") // Add hyphen between words + } + } + return str.String() +} |