Simplify, switch to correct crypto lib

Amolith created

Change summary

go.mod  |   2 +
go.sum  |   2 +
main.go | 115 ++++++++++++++++++----------------------------------------
3 files changed, 41 insertions(+), 78 deletions(-)

Detailed changes

go.mod 🔗

@@ -5,3 +5,5 @@
 module git.sr.ht/~amolith/eow
 
 go 1.23.1
+
+require github.com/spf13/pflag v1.0.5

go.sum 🔗

@@ -0,0 +1,2 @@
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=

main.go 🔗

@@ -5,136 +5,95 @@
 package main
 
 import (
+	"crypto/rand"
 	"fmt"
-	"math/rand"
+	"math/big"
 	"os"
-	"strconv"
 	"strings"
-	"time"
+
+	flag "github.com/spf13/pflag"
 )
 
 var (
-	length         = 20
 	version        = ""
 	consonants     = "bcdfghjklmnpqrstvwxyz"
 	vowels         = "aeiou"
 	numbers        = "0123456789"
 	newBase60Chars = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz"
+	flagNB60       = flag.BoolP("nb60", "n", false, "Generate a 20-character password using the NewBase60 charset")
+	flagFriendly   = flag.BoolP("friendly", "f", true, "Generate a 20-character password using an Apple-style friendly algorithm")
 )
 
 func main() {
-	if len(os.Args) > 1 && os.Args[1] == "-h" {
-		printHelp()
-		os.Exit(0)
-	} else if len(os.Args) > 1 && os.Args[1] == "-t" {
-		switch os.Args[2] {
-		case "nb60":
-			fmt.Println(generate(length))
-		case "friendly":
-			fmt.Println(generateFriendly())
-		}
-	} else if len(os.Args) > 3 {
-		fmt.Print("Error: too many arguments...\n\n")
-		printHelp()
-		os.Exit(1)
-	} else if len(os.Args) == 2 {
-		// Convert the string argument to an int
-		length, err := strconv.Atoi(os.Args[1])
-		if err != nil {
-			fmt.Println("Error: length must be an integer")
-			os.Exit(1)
-		}
-		fmt.Println(generate(length))
-		os.Exit(0)
-	} else if len(os.Args) == 3 {
-		length, err := strconv.Atoi(os.Args[1])
-		if err != nil {
-			fmt.Println("Error: length must be an integer")
-			os.Exit(1)
-		}
-		count, err := strconv.Atoi(os.Args[2])
-		if err != nil {
-			fmt.Println("Error: count must be an integer")
-			os.Exit(1)
-		}
-		for i := 0; i < count; i++ {
-			fmt.Println(generate(length))
-		}
-		os.Exit(0)
-	} else if len(os.Args) == 1 {
-		fmt.Println(generate(length))
+	flag.Parse()
+	if *flagNB60 {
+		fmt.Println(generateNB60())
 		os.Exit(0)
 	}
+	fmt.Println(generateFriendly())
 }
 
-func generate(length int) string {
-	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
-
+func generateNB60() string {
 	var sb strings.Builder
-	sb.Grow(length)
+	sb.Grow(20)
 
-	for i := 0; i < length; i++ {
-		sb.WriteByte(newBase60Chars[rng.Intn(len(newBase60Chars))])
+	for i := 0; i < 20; i++ {
+		sb.WriteByte(newBase60Chars[randomInt(len(newBase60Chars))])
 	}
 
 	return sb.String()
 }
 
 func generateFriendly() string {
-	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
-
 	sets := make([]string, 3)
 	for i := 0; i < 3; i++ {
-		sets[i] = genFriendlySet(rng)
+		sets[i] = genFriendlySet()
 	}
 
 	result := strings.Join(sets, "-")
 
-	letterPos := rng.Intn(len(result))
+	letterPos := randomInt(len(result))
+
 	// Generate a different position until a non-separator is found
 	for result[letterPos] == '-' {
-		letterPos = rng.Intn(len(result))
+		letterPos = randomInt(len(result))
 	}
 	existingLetter := result[letterPos]
 	var newLetter string
 	if strings.Contains(consonants, string(existingLetter)) {
-		newLetter = string(consonants[rng.Intn(len(consonants))])
+		newLetter = string(consonants[randomInt(len(consonants))])
 	} else if strings.Contains(vowels, string(existingLetter)) {
-		newLetter = string(vowels[rng.Intn(len(vowels))])
+		newLetter = string(vowels[randomInt(len(vowels))])
 	}
 	result = result[:letterPos] + strings.ToUpper(newLetter) + result[letterPos+1:]
 
 	// Generate a different position until a non-separator and non-uppercase-letter
 	// is found
-	numberPos := rng.Intn(len(result))
+	numberPos := randomInt(len(result))
 	for result[numberPos] == '-' || result[numberPos] == result[letterPos] {
-		numberPos = rng.Intn(len(result))
+		numberPos = randomInt(len(result))
 	}
-	result = result[:numberPos] + string(numbers[rng.Intn(len(numbers))]) + result[numberPos+1:]
+	result = result[:numberPos] + string(numbers[randomInt(len(numbers))]) + result[numberPos+1:]
 
 	return result
 }
 
-func genFriendlySet(rng *rand.Rand) string {
+func genFriendlySet() string {
 	set := make([]byte, 6)
-	set[0] = consonants[rng.Intn(len(consonants))]
-	set[1] = vowels[rng.Intn(len(vowels))]
-	set[2] = consonants[rng.Intn(len(consonants))]
-	set[3] = consonants[rng.Intn(len(consonants))]
-	set[4] = vowels[rng.Intn(len(vowels))]
-	set[5] = consonants[rng.Intn(len(consonants))]
+	set[0] = consonants[randomInt(len(consonants))]
+	set[1] = vowels[randomInt(len(vowels))]
+	set[2] = consonants[randomInt(len(consonants))]
+	set[3] = consonants[randomInt(len(consonants))]
+	set[4] = vowels[randomInt(len(vowels))]
+	set[5] = consonants[randomInt(len(consonants))]
 	return string(set)
 }
 
-func printHelp() {
-	fmt.Println(`Usage: eow [-h|-t] <length> <count>
-
--h prints this help message
-
--t <type> prints a single 20-character NewBase60 (nb60) or friendly (friendly)
-  		  password.
-
-Arguments are positional, so length AND count may be omitted OR count may be
-omitted. If specifying count, length must also be specified. When ommitted,
-length defaults to 20 and count defaults to 1.`)
+func randomInt(max int) int {
+	n, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
+	if err != nil {
+		fmt.Println("Error: could not generate a random number:", err)
+		os.Exit(1)
+	}
+	return int(n.Int64())
 }