Last active
February 2, 2023 22:35
-
-
Save denisbrodbeck/635a644089868a51eccd6ae22b2eb800 to your computer and use it in GitHub Desktop.
How to generate secure random strings in golang with crypto/rand.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// License: MIT | |
package main | |
import ( | |
"crypto/rand" | |
"fmt" | |
"math/big" | |
) | |
// GenerateRandomASCIIString returns a securely generated random ASCII string. | |
// It reads random numbers from crypto/rand and searches for printable characters. | |
// It will return an error if the system's secure random number generator fails to | |
// function correctly, in which case the caller must not continue. | |
func GenerateRandomASCIIString(length int) (string, error) { | |
result := "" | |
for { | |
if len(result) >= length { | |
return result, nil | |
} | |
num, err := rand.Int(rand.Reader, big.NewInt(int64(127))) | |
if err != nil { | |
return "", err | |
} | |
n := num.Int64() | |
// Make sure that the number/byte/letter is inside | |
// the range of printable ASCII characters (excluding space and DEL) | |
if n > 32 && n < 127 { | |
result += string(n) | |
} | |
} | |
} | |
func main() { | |
length := 20 | |
random, err := GenerateRandomASCIIString(length) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Println(random) | |
// Output: 0_tRSWiyJ=b4(x^6TE<q | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// License: MIT | |
package main | |
import ( | |
"crypto/rand" | |
"fmt" | |
"math/big" | |
"sort" | |
"strings" | |
"gonum.org/v1/plot" | |
"gonum.org/v1/plot/plotter" | |
"gonum.org/v1/plot/plotutil" | |
"gonum.org/v1/plot/vg" | |
) | |
const iterations = 1000 * 100 | |
type generate func(int) (string, error) | |
func GenerateRandomStringEven(length int) (pass string, err error) { | |
for { | |
if length <= lenString(pass) { | |
return pass, nil | |
} | |
num, err := rand.Int(rand.Reader, big.NewInt(int64(127))) | |
if err != nil { | |
return "", err | |
} | |
n := num.Int64() | |
if n > 32 && n < 127 { | |
pass += string(n) | |
} | |
} | |
} | |
//const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" | |
const letters = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" | |
func GenerateRandomStringUneven(length int) (string, error) { | |
bytes := make([]byte, length) | |
if _, err := rand.Read(bytes); err != nil { | |
return "", err | |
} | |
for i, b := range bytes { | |
bytes[i] = letters[b%byte(len(letters))] | |
} | |
return string(bytes), nil | |
} | |
func main() { | |
fmt.Println("Calculating even distribution of random characters…") | |
even := measureDistribution(iterations, GenerateRandomStringEven) | |
print(even) | |
draw(even, "Even Distribution", "dist-even.png") | |
fmt.Println("\n\nCalculating uneven distribution of (nearly) random characters…") | |
uneven := measureDistribution(iterations, GenerateRandomStringUneven) | |
print(uneven) | |
draw(uneven, "Uneven Distribution", "dist-uneven.png") | |
} | |
func measureDistribution(iterations int, fn generate) map[string]int { | |
dist := make(map[string]int) | |
for index := 1; index <= iterations; index++ { | |
// status output to cli | |
if index%1000 == 0 { | |
fmt.Printf("\r%d / %d", index, iterations) | |
} | |
raw, err := fn(100) | |
if err != nil { | |
panic(err) | |
} | |
for _, s := range raw { | |
c := string(s) | |
i := dist[c] | |
dist[c] = i + 1 | |
} | |
} | |
return dist | |
} | |
func draw(distribution map[string]int, title, filename string) { | |
keys, values := orderMap(distribution) | |
group := plotter.Values{} | |
for _, v := range values { | |
group = append(group, float64(v)) | |
} | |
p, err := plot.New() | |
if err != nil { | |
panic(err) | |
} | |
p.Title.Text = title | |
p.Y.Label.Text = "N" | |
bars, err := plotter.NewBarChart(group, vg.Points(4)) | |
if err != nil { | |
panic(err) | |
} | |
bars.LineStyle.Width = vg.Length(0) | |
bars.Color = plotutil.Color(0) | |
p.Add(bars) | |
p.NominalX(keys...) | |
if err := p.Save(300*vg.Millimeter, 150*vg.Millimeter, filename); err != nil { | |
panic(err) | |
} | |
} | |
func orderMap(m map[string]int) (keys []string, values []int) { | |
keys = []string{} | |
values = []int{} | |
for k := range m { | |
keys = append(keys, k) | |
} | |
sort.Strings(keys) | |
for _, key := range keys { | |
values = append(values, m[key]) | |
} | |
return keys, values | |
} | |
func print(m map[string]int) { | |
fmt.Println("\n\nCharacter Distribution") | |
keys, values := orderMap(m) | |
for i, key := range keys { | |
fmt.Println(key, "\t", values[i]) | |
} | |
fmt.Println("\nAlphabet:", strings.Join(keys, "")) | |
} | |
// lenString returns the amount of valid characters instead of the number of bytes (like len()). | |
func lenString(s string) int { | |
return len([]rune(s)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Actually you don't need a
big.Int
just to generate random numbers between 32 and 126.You also don't want to pressurize the GC by doing a lot of
s += string(i)
.Even further, you don't want to do UTF-8 encoding each time by just calling
string(i)
.This is my implementation: