1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
package passgen
import (
"crypto/rand"
"errors"
"fmt"
"io"
"math"
)
const (
lowerChars = "abcdefghijklmnopqrstuvwxyz"
upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
digitChars = "0123456789"
symbolChars = "!@#$%^&*()-_=+[]{};:,.?/"
)
var ErrEmptyAlphabet = errors.New("alphabet is empty")
type Options struct {
Length int
UseLower bool
UseUpper bool
UseDigits bool
UseSymbols bool
Random io.Reader
}
func DefaultOptions() Options {
return Options{
Length: 24,
UseLower: true,
UseUpper: true,
UseDigits: true,
UseSymbols: true,
}
}
func Generate(opts Options) (string, error) {
if opts.Length <= 0 {
return "", fmt.Errorf("length must be positive")
}
alphabet := buildAlphabet(opts)
if alphabet == "" {
return "", ErrEmptyAlphabet
}
random := opts.Random
if random == nil {
random = rand.Reader
}
password := make([]byte, opts.Length)
for i := range password {
n, err := randomIndex(len(alphabet), random)
if err != nil {
return "", fmt.Errorf("choose password character: %w", err)
}
password[i] = alphabet[n]
}
return string(password), nil
}
func EntropyBits(length int, alphabetSize int) (float64, error) {
if length <= 0 {
return 0, fmt.Errorf("length must be positive")
}
if alphabetSize <= 1 {
return 0, fmt.Errorf("alphabet size must be greater than 1")
}
return float64(length) * math.Log2(float64(alphabetSize)), nil
}
func AlphabetSize(opts Options) int {
return len(buildAlphabet(opts))
}
func buildAlphabet(opts Options) string {
alphabet := ""
if opts.UseLower {
alphabet += lowerChars
}
if opts.UseUpper {
alphabet += upperChars
}
if opts.UseDigits {
alphabet += digitChars
}
if opts.UseSymbols {
alphabet += symbolChars
}
return alphabet
}
func randomIndex(size int, random io.Reader) (int, error) {
if size <= 0 {
return 0, ErrEmptyAlphabet
}
if size > 256 {
return 0, fmt.Errorf("alphabet size %d exceeds supported maximum 256", size)
}
cutoff := 256 - (256 % size)
for {
value, err := randomByte(random)
if err != nil {
return 0, err
}
if int(value) < cutoff {
return int(value) % size, nil
}
}
}
func randomByte(random io.Reader) (byte, error) {
var b [1]byte
if _, err := io.ReadFull(random, b[:]); err != nil {
return 0, fmt.Errorf("read random byte: %w", err)
}
return b[0], nil
}
|