go.mod 🔗
@@ -5,3 +5,5 @@
module git.sr.ht/~amolith/eow
go 1.23.1
+
+require github.com/spf13/pflag v1.0.5
Amolith created
go.mod | 2 +
go.sum | 2 +
main.go | 115 ++++++++++++++++++----------------------------------------
3 files changed, 41 insertions(+), 78 deletions(-)
@@ -5,3 +5,5 @@
module git.sr.ht/~amolith/eow
go 1.23.1
+
+require github.com/spf13/pflag v1.0.5
@@ -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=
@@ -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())
}