Skip to content

Instantly share code, notes, and snippets.

@denisbrodbeck
Last active February 2, 2023 22:35
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save denisbrodbeck/635a644089868a51eccd6ae22b2eb800 to your computer and use it in GitHub Desktop.
Save denisbrodbeck/635a644089868a51eccd6ae22b2eb800 to your computer and use it in GitHub Desktop.
How to generate secure random strings in golang with crypto/rand.
// 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
}
// 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))
}
@aeneasr
Copy link

aeneasr commented Feb 11, 2020

if n > 32 && n < 127 {

is kind of wasteful, why not

r, _ := rand.Int(rander, big.NewInt(int64(len(letters))))
result = result + string(letters[r])

?

@m13253
Copy link

m13253 commented Apr 26, 2021

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:

import "crypto/rand"

func GenerateRandomASCIIString(length int) (string, error) {
	result := make([]byte, length)
	_, err := rand.Read(result)
	if err != nil {
		return "", err
	}
	for i := 0; i < length; i++ {
		result[i] &= 0x7F
		for result[i] < 32 || result[i] == 127 {
			_, err = rand.Read(result[i : i+1])
			if err != nil {
				return "", err
			}
			result[i] &= 0x7F
		}
	}
	return string(result), nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment