Skip to content

Commit ca6b9e9

Browse files
authored
chore: use robust RNG in cryptorand (#8040)
1 parent c8e6783 commit ca6b9e9

File tree

4 files changed

+78
-325
lines changed

4 files changed

+78
-325
lines changed

cryptorand/errors_test.go

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,31 +30,6 @@ func TestRandError(t *testing.T) {
3030
require.ErrorIs(t, err, io.ErrShortBuffer, "expected Int63 error")
3131
})
3232

33-
t.Run("Uint64", func(t *testing.T) {
34-
_, err := cryptorand.Uint64()
35-
require.ErrorIs(t, err, io.ErrShortBuffer, "expected Uint64 error")
36-
})
37-
38-
t.Run("Int31", func(t *testing.T) {
39-
_, err := cryptorand.Int31()
40-
require.ErrorIs(t, err, io.ErrShortBuffer, "expected Int31 error")
41-
})
42-
43-
t.Run("Int31n", func(t *testing.T) {
44-
_, err := cryptorand.Int31n(100)
45-
require.ErrorIs(t, err, io.ErrShortBuffer, "expected Int31n error")
46-
})
47-
48-
t.Run("Uint32", func(t *testing.T) {
49-
_, err := cryptorand.Uint32()
50-
require.ErrorIs(t, err, io.ErrShortBuffer, "expected Uint32 error")
51-
})
52-
53-
t.Run("Int", func(t *testing.T) {
54-
_, err := cryptorand.Int()
55-
require.ErrorIs(t, err, io.ErrShortBuffer, "expected Int error")
56-
})
57-
5833
t.Run("Intn_32bit", func(t *testing.T) {
5934
_, err := cryptorand.Intn(100)
6035
require.ErrorIs(t, err, io.ErrShortBuffer, "expected Intn error")
@@ -70,11 +45,6 @@ func TestRandError(t *testing.T) {
7045
require.ErrorIs(t, err, io.ErrShortBuffer, "expected Float64 error")
7146
})
7247

73-
t.Run("Float32", func(t *testing.T) {
74-
_, err := cryptorand.Float32()
75-
require.ErrorIs(t, err, io.ErrShortBuffer, "expected Float32 error")
76-
})
77-
7848
t.Run("StringCharset", func(t *testing.T) {
7949
_, err := cryptorand.HexString(10)
8050
require.ErrorIs(t, err, io.ErrShortBuffer, "expected HexString error")

cryptorand/numbers.go

Lines changed: 31 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -3,194 +3,58 @@ package cryptorand
33
import (
44
"crypto/rand"
55
"encoding/binary"
6-
"time"
7-
8-
"golang.org/x/xerrors"
6+
insecurerand "math/rand"
97
)
108

11-
// Most of this code is inspired by math/rand, so shares similar
12-
// functions and implementations, but uses crypto/rand to generate
13-
// random Int63 data.
14-
15-
// Int64 returns a non-negative random 63-bit integer as a int64.
16-
func Int63() (int64, error) {
17-
var i int64
18-
err := binary.Read(rand.Reader, binary.BigEndian, &i)
19-
if err != nil {
20-
return 0, xerrors.Errorf("read binary: %w", err)
21-
}
22-
23-
if i < 0 {
24-
return -i, nil
25-
}
26-
return i, nil
9+
type cryptoSource struct {
10+
err error
2711
}
2812

29-
// Uint64 returns a random 64-bit integer as a uint64.
30-
func Uint64() (uint64, error) {
31-
upper, err := Int63()
32-
if err != nil {
33-
return 0, xerrors.Errorf("read upper: %w", err)
34-
}
35-
36-
lower, err := Int63()
37-
if err != nil {
38-
return 0, xerrors.Errorf("read lower: %w", err)
39-
}
40-
41-
return uint64(lower)>>31 | uint64(upper)<<32, nil
13+
func (*cryptoSource) Seed(_ int64) {
14+
// Intentionally disregard seed
4215
}
4316

44-
// Int31 returns a non-negative random 31-bit integer as a int32.
45-
func Int31() (int32, error) {
46-
i, err := Int63()
17+
func (c *cryptoSource) Int63() int64 {
18+
var n int64
19+
err := binary.Read(rand.Reader, binary.BigEndian, &n)
4720
if err != nil {
48-
return 0, err
21+
c.err = err
4922
}
50-
51-
return int32(i >> 32), nil
23+
// The sign bit must be cleared to ensure the final value is non-negative.
24+
n &= 0x7fffffffffffffff
25+
return n
5226
}
5327

54-
// Uint32 returns a 32-bit value as a uint32.
55-
func Uint32() (uint32, error) {
56-
i, err := Int63()
28+
func (c *cryptoSource) Uint64() uint64 {
29+
var n uint64
30+
err := binary.Read(rand.Reader, binary.BigEndian, &n)
5731
if err != nil {
58-
return 0, err
32+
c.err = err
5933
}
60-
61-
return uint32(i >> 31), nil
62-
}
63-
64-
// Int returns a non-negative random integer as a int.
65-
func Int() (int, error) {
66-
i, err := Int63()
67-
if err != nil {
68-
return 0, err
69-
}
70-
71-
if i < 0 {
72-
return int(-i), nil
73-
}
74-
return int(i), nil
75-
}
76-
77-
// Int63n returns a non-negative random integer in [0,max) as a int64.
78-
func Int63n(max int64) (int64, error) {
79-
if max <= 0 {
80-
panic("invalid argument to Int63n")
81-
}
82-
83-
trueMax := int64((1 << 63) - 1 - (1<<63)%uint64(max))
84-
i, err := Int63()
85-
if err != nil {
86-
return 0, err
87-
}
88-
89-
for i > trueMax {
90-
i, err = Int63()
91-
if err != nil {
92-
return 0, err
93-
}
94-
}
95-
96-
return i % max, nil
34+
return n
9735
}
9836

99-
// Int31n returns a non-negative integer in [0,max) as a int32.
100-
func Int31n(max int32) (int32, error) {
101-
i, err := Uint32()
102-
if err != nil {
103-
return 0, err
104-
}
105-
106-
return UnbiasedModulo32(i, max)
37+
// secureRand returns a cryptographically secure random number generator.
38+
func secureRand() (*insecurerand.Rand, *cryptoSource) {
39+
var cs cryptoSource
40+
//nolint:gosec
41+
return insecurerand.New(&cs), &cs
10742
}
10843

109-
// UnbiasedModulo32 uniformly modulos v by n over a sufficiently large data
110-
// set, regenerating v if necessary. n must be > 0. All input bits in v must be
111-
// fully random, you cannot cast a random uint8/uint16 for input into this
112-
// function.
113-
//
114-
//nolint:varnamelen
115-
func UnbiasedModulo32(v uint32, n int32) (int32, error) {
116-
prod := uint64(v) * uint64(n)
117-
low := uint32(prod)
118-
if low < uint32(n) {
119-
thresh := uint32(-n) % uint32(n)
120-
for low < thresh {
121-
var err error
122-
v, err = Uint32()
123-
if err != nil {
124-
return 0, err
125-
}
126-
prod = uint64(v) * uint64(n)
127-
low = uint32(prod)
128-
}
129-
}
130-
return int32(prod >> 32), nil
44+
// Int64 returns a non-negative random 63-bit integer as a int64.
45+
func Int63() (int64, error) {
46+
rng, cs := secureRand()
47+
return rng.Int63(), cs.err
13148
}
13249

133-
// Intn returns a non-negative integer in [0,max) as a int.
50+
// Intn returns a non-negative integer in [0,max) as an int.
13451
func Intn(max int) (int, error) {
135-
if max <= 0 {
136-
panic("n must be a positive nonzero number")
137-
}
138-
139-
if max <= 1<<31-1 {
140-
i, err := Int31n(int32(max))
141-
if err != nil {
142-
return 0, err
143-
}
144-
145-
return int(i), nil
146-
}
147-
148-
i, err := Int63n(int64(max))
149-
if err != nil {
150-
return 0, err
151-
}
152-
153-
return int(i), nil
52+
rng, cs := secureRand()
53+
return rng.Intn(max), cs.err
15454
}
15555

15656
// Float64 returns a random number in [0.0,1.0) as a float64.
15757
func Float64() (float64, error) {
158-
again:
159-
i, err := Int63n(1 << 53)
160-
if err != nil {
161-
return 0, err
162-
}
163-
164-
f := (float64(i) / (1 << 53))
165-
if f == 1 {
166-
goto again
167-
}
168-
169-
return f, nil
170-
}
171-
172-
// Float32 returns a random number in [0.0,1.0) as a float32.
173-
func Float32() (float32, error) {
174-
again:
175-
i, err := Float64()
176-
if err != nil {
177-
return 0, err
178-
}
179-
180-
f := float32(i)
181-
if f == 1 {
182-
goto again
183-
}
184-
185-
return f, nil
186-
}
187-
188-
// Duration returns a random time.Duration value
189-
func Duration() (time.Duration, error) {
190-
i, err := Int63()
191-
if err != nil {
192-
return time.Duration(0), err
193-
}
194-
195-
return time.Duration(i), nil
58+
rng, cs := secureRand()
59+
return rng.Float64(), cs.err
19660
}

0 commit comments

Comments
 (0)