aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--go/makepass.go294
-rwxr-xr-xmakepass.zsh2
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 <https://dnns.no>`)
+ 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!
#