1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package main
6
7import (
8 "crypto/rand"
9 "fmt"
10 "math/big"
11 "os"
12 "strings"
13
14 flag "github.com/spf13/pflag"
15)
16
17var (
18 version = ""
19 consonants = "bcdfghjklmnpqrstvwxyz"
20 vowels = "aeiou"
21 numbers = "0123456789"
22 newBase60Chars = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz"
23 flagNB60 = flag.BoolP("nb60", "n", false, "Generate a 20-character password using the NewBase60 charset")
24 flagFriendly = flag.BoolP("friendly", "f", true, "Generate a 20-character password using an Apple-style friendly algorithm")
25)
26
27func main() {
28 flag.Parse()
29 if *flagNB60 {
30 fmt.Println(generateNB60())
31 os.Exit(0)
32 }
33 fmt.Println(generateFriendly())
34}
35
36func generateNB60() string {
37 var sb strings.Builder
38 sb.Grow(20)
39
40 for i := 0; i < 20; i++ {
41 sb.WriteByte(newBase60Chars[randomInt(len(newBase60Chars))])
42 }
43
44 return sb.String()
45}
46
47func generateFriendly() string {
48 sets := make([]string, 3)
49 for i := 0; i < 3; i++ {
50 sets[i] = genFriendlySet()
51 }
52
53 result := strings.Join(sets, "-")
54
55 letterPos := randomInt(len(result))
56
57 // Generate a different position until a non-separator is found
58 for result[letterPos] == '-' {
59 letterPos = randomInt(len(result))
60 }
61 existingLetter := result[letterPos]
62 var newLetter string
63 if strings.Contains(consonants, string(existingLetter)) {
64 newLetter = string(consonants[randomInt(len(consonants))])
65 } else if strings.Contains(vowels, string(existingLetter)) {
66 newLetter = string(vowels[randomInt(len(vowels))])
67 }
68 result = result[:letterPos] + strings.ToUpper(newLetter) + result[letterPos+1:]
69
70 // Generate a different position until a non-separator and non-uppercase-letter
71 // is found
72 numberPos := randomInt(len(result))
73 for result[numberPos] == '-' || result[numberPos] == result[letterPos] {
74 numberPos = randomInt(len(result))
75 }
76 result = result[:numberPos] + string(numbers[randomInt(len(numbers))]) + result[numberPos+1:]
77
78 return result
79}
80
81func genFriendlySet() string {
82 set := make([]byte, 6)
83 set[0] = consonants[randomInt(len(consonants))]
84 set[1] = vowels[randomInt(len(vowels))]
85 set[2] = consonants[randomInt(len(consonants))]
86 set[3] = consonants[randomInt(len(consonants))]
87 set[4] = vowels[randomInt(len(vowels))]
88 set[5] = consonants[randomInt(len(consonants))]
89 return string(set)
90}
91
92func randomInt(max int) int {
93 n, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
94 if err != nil {
95 fmt.Println("Error: could not generate a random number:", err)
96 os.Exit(1)
97 }
98 return int(n.Int64())
99}