-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenerate.go
141 lines (133 loc) · 4.43 KB
/
generate.go
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package cryptorandomstring
import (
"crypto/rand"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"fmt"
"math"
"strings"
)
func (g *Generator) Generate() (string, error) {
// 1: check if length >= 0 && length is a finite number, handled as the data type is uint64
// 2: check if both, kind and characters are passed
if g.kind != "" && g.characters != "" {
return "", fmt.Errorf("expected either `kind` or `characters`")
}
// 3: check if characters is passed and is of type string, handled as the data type is string
// 4: check if kind is one of the allowed types
if !allowedTypes[g.kind] {
return "", fmt.Errorf("unknown type: %s", g.kind)
}
// 5: check if both, kind and characters are not passed
if g.kind == "" && g.characters == "" {
g.WithKind(defaultKind)
}
// 6: check if kind is equal to hex
if g.kind == "hex" {
if str, err := generateRandomBytes(uint64(math.Ceil(float64(g.length)*0.5)), "hex", g.length); err != nil {
return "", err
} else {
return str, nil
}
}
// 7: check if kind is equal to base64
if g.kind == "base64" {
if str, err := generateRandomBytes(uint64(math.Ceil(float64(g.length)*0.75)), "base64", g.length); err != nil {
return "", err
} else {
return str, nil
}
}
// 8: check if kind is equal to url-safe
if g.kind == "url-safe" {
if str, err := generateForCustomCharacters(g.length, urlSafeCharacters); err != nil {
return "", err
} else {
return str, nil
}
}
// 9: check if kind is equal to numeric
if g.kind == "numeric" {
if str, err := generateForCustomCharacters(g.length, numericCharacters); err != nil {
return "", err
} else {
return str, nil
}
}
// 10: check if kind is equal to distinguishable
if g.kind == "distinguishable" {
if str, err := generateForCustomCharacters(g.length, distinguishableCharacters); err != nil {
return "", err
} else {
return str, nil
}
}
// 11: check if kind is equal to ascii-printable
if g.kind == "ascii-printable" {
if str, err := generateForCustomCharacters(g.length, asciiPrintableCharacters); err != nil {
return "", err
} else {
return str, nil
}
}
// 12: check if kind is equal to alphanumeric
if g.kind == "alphanumeric" {
if str, err := generateForCustomCharacters(g.length, alphanumericCharacters); err != nil {
return "", err
} else {
return str, nil
}
}
// 13: check if length of characters is 0, handled by #5
// 14: check if length of characters is greater than 65536
if len(g.characters) > 0x10000 {
return "", fmt.Errorf("expected `characters` string length to be less or equal to 65536")
}
if str, err := generateForCustomCharacters(g.length, strings.Split(g.characters, "")); err != nil {
return "", err
} else {
return str, nil
}
}
func Generate() (string, error) {
return defaultGenerator.Generate()
}
func generateRandomBytes(byteLength uint64, kind string, length uint64) (string, error) {
randomBytes := make([]byte, byteLength)
if _, err := rand.Read(randomBytes); err != nil {
return "", err
}
if kind == "hex" {
return hex.EncodeToString(randomBytes)[0:length], nil
} else if kind == "base64" {
return base64.StdEncoding.EncodeToString(randomBytes)[0:length], nil
} else {
return "", nil
}
}
func generateForCustomCharacters(length uint64, characters []string) (string, error) {
// Generating entropy is faster than complex math operations, so we use the simplest way
characterCount := len(characters)
maxValidSelector := uint16((math.Floor(0x10000/float64(characterCount)) * float64(characterCount)) - 1) // Using values above this will ruin distribution when using modular division
entropyLength := int(2 * math.Ceil(1.1*float64(length))) // Generating a bit more than required so chances we need more than one pass will be really low
var generatedString string
var generatedStringLength uint64
for generatedStringLength < length {
randomBytes := make([]byte, entropyLength)
if _, err := rand.Read(randomBytes); err != nil {
return "", err
}
entropyPosition := 0
for entropyPosition < entropyLength && generatedStringLength < length {
entropyValue := binary.LittleEndian.Uint16(randomBytes[entropyPosition:])
entropyPosition += 2
if entropyValue > maxValidSelector { // Skip values which will ruin distribution when using modular division
continue
}
generatedString += characters[entropyValue%uint16(characterCount)]
generatedStringLength++
}
}
return generatedString, nil
}