Skip to content

Commit 50087aa

Browse files
seankhliaogopherbot
authored andcommitted
crypto/rand: add Text for secure random strings
Fixes #67057 Change-Id: Id4a1d07bc45d9ebf90b7e6ef507002908dcfa12d Reviewed-on: https://go-review.googlesource.com/c/go/+/627477 Auto-Submit: Ian Lance Taylor <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Russ Cox <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 0db2501 commit 50087aa

File tree

4 files changed

+95
-0
lines changed

4 files changed

+95
-0
lines changed

api/next/67057.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg crypto/rand, func Text() string #67057
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The new [Text] function can be used to generate cryptographically secure random text strings. <!-- go.dev/issue/67057 -->

src/crypto/rand/text.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package rand
6+
7+
const base32alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
8+
9+
// Text returns a cryptographically random string using the standard RFC 4648 base32 alphabet
10+
// for use when a secret string, token, password, or other text is needed.
11+
// The result contains at least 128 bits of randomness, enough to prevent brute force
12+
// guessing attacks and to make the likelihood of collisions vanishingly small.
13+
// A future version may return longer texts as needed to maintain those properties.
14+
func Text() string {
15+
// ⌈log₃₂ 2¹²⁸⌉ = 26 chars
16+
src := make([]byte, 26)
17+
Read(src)
18+
for i := range src {
19+
src[i] = base32alphabet[src[i]%32]
20+
}
21+
return string(src)
22+
}

src/crypto/rand/text_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package rand_test
6+
7+
import (
8+
"crypto/rand"
9+
"fmt"
10+
"testing"
11+
)
12+
13+
func TestText(t *testing.T) {
14+
set := make(map[string]struct{}) // hold every string produced
15+
var indexSet [26]map[rune]int // hold every char produced at every position
16+
for i := range indexSet {
17+
indexSet[i] = make(map[rune]int)
18+
}
19+
20+
// not getting a char in a position: (31/32)¹⁰⁰⁰ = 1.6e-14
21+
// test completion within 1000 rounds: (1-(31/32)¹⁰⁰⁰)²⁶ = 0.9999999999996
22+
// empirically, this should complete within 400 rounds = 0.999921
23+
rounds := 1000
24+
var done bool
25+
for range rounds {
26+
s := rand.Text()
27+
if len(s) != 26 {
28+
t.Errorf("len(Text()) = %d, want = 26", len(s))
29+
}
30+
for i, r := range s {
31+
if ('A' > r || r > 'Z') && ('2' > r || r > '7') {
32+
t.Errorf("Text()[%d] = %v, outside of base32 alphabet", i, r)
33+
}
34+
}
35+
if _, ok := set[s]; ok {
36+
t.Errorf("Text() = %s, duplicate of previously produced string", s)
37+
}
38+
set[s] = struct{}{}
39+
40+
done = true
41+
for i, r := range s {
42+
indexSet[i][r]++
43+
if len(indexSet[i]) != 32 {
44+
done = false
45+
}
46+
}
47+
if done {
48+
break
49+
}
50+
}
51+
if !done {
52+
t.Errorf("failed to produce every char at every index after %d rounds", rounds)
53+
indexSetTable(t, indexSet)
54+
}
55+
}
56+
57+
func indexSetTable(t *testing.T, indexSet [26]map[rune]int) {
58+
alphabet := "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
59+
line := " "
60+
for _, r := range alphabet {
61+
line += fmt.Sprintf(" %3s", string(r))
62+
}
63+
t.Log(line)
64+
for i, set := range indexSet {
65+
line = fmt.Sprintf("%2d:", i)
66+
for _, r := range alphabet {
67+
line += fmt.Sprintf(" %3d", set[r])
68+
}
69+
t.Log(line)
70+
}
71+
}

0 commit comments

Comments
 (0)