]> git.feebdaed.xyz Git - 0xmirror/go.git/commitdiff
crypto,testing/cryptotest: ignore random io.Reader params, add SetGlobalRandom
authorFilippo Valsorda <filippo@golang.org>
Mon, 15 Sep 2025 16:58:04 +0000 (18:58 +0200)
committerGopher Robot <gobot@golang.org>
Thu, 27 Nov 2025 00:01:17 +0000 (16:01 -0800)
First, we centralize all random bytes generation through drbg.Read. The
rest of the FIPS 140-3 module can't use external functions anyway, so
drbg.Read needs to have all the logic.

Then, make sure that the crypto/... tree uses drbg.Read (or the new
crypto/internal/rand.Reader wrapper) instead of crypto/rand, so it is
unaffected by applications setting crypto/rand.Reader.

Next, pass all unspecified random io.Reader parameters through the new
crypto/internal/rand.CustomReader, which just redirects to drbg.Read
unless GODEBUG=cryptocustomrand=1 is set. Move all the calls to
MaybeReadByte there, since it's only needed for these custom Readers.

Finally, add testing/cryptotest.SetGlobalRandom which sets
crypto/rand.Reader to a locked deterministic source and overrides
drbg.Read. This way SetGlobalRandom should affect all cryptographic
randomness in the standard library.

Fixes #70942

Co-authored-by: qiulaidongfeng <2645477756@qq.com>
Change-Id: I6a6a69641311d9fac318abcc6d79677f0e406100
Reviewed-on: https://go-review.googlesource.com/c/go/+/724480
Reviewed-by: Nicholas Husin <nsh@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

36 files changed:
api/next/70942.txt [new file with mode: 0644]
doc/godebug.md
doc/next/6-stdlib/99-minor/crypto/dsa/70924.md [new file with mode: 0644]
doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md [new file with mode: 0644]
doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md [new file with mode: 0644]
doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md [new file with mode: 0644]
doc/next/6-stdlib/99-minor/crypto/rand/70924.md [new file with mode: 0644]
doc/next/6-stdlib/99-minor/crypto/rsa/70924.md [new file with mode: 0644]
doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md [new file with mode: 0644]
src/crypto/dsa/dsa.go
src/crypto/ecdh/ecdh.go
src/crypto/ecdh/nist.go
src/crypto/ecdh/x25519.go
src/crypto/ecdsa/ecdsa.go
src/crypto/ecdsa/ecdsa_legacy.go
src/crypto/ed25519/ed25519.go
src/crypto/hpke/kem.go
src/crypto/hpke/pq.go
src/crypto/internal/fips140/drbg/rand.go
src/crypto/internal/fips140/rsa/pkcs1v22.go
src/crypto/internal/rand/rand.go [new file with mode: 0644]
src/crypto/internal/rand/rand_fipsv1.0.go [new file with mode: 0644]
src/crypto/internal/rand/rand_fipsv2.0.go [new file with mode: 0644]
src/crypto/internal/sysrand/rand.go
src/crypto/mlkem/mlkem.go
src/crypto/rand/rand.go
src/crypto/rand/util.go
src/crypto/rsa/pkcs1v15.go
src/crypto/rsa/rsa.go
src/crypto/tls/handshake_test.go
src/go/build/deps_test.go
src/internal/godebugs/table.go
src/runtime/metrics/doc.go
src/testing/cryptotest/rand.go [new file with mode: 0644]
src/testing/cryptotest/rand_test.go [new file with mode: 0644]
src/testing/testing.go

diff --git a/api/next/70942.txt b/api/next/70942.txt
new file mode 100644 (file)
index 0000000..ac212d9
--- /dev/null
@@ -0,0 +1 @@
+pkg testing/cryptotest, func SetGlobalRandom(*testing.T, uint64) #70942
index 0d3354bc0fd1c03d1c354ad4f02d4c4052b4b1f2..d6bb18603c3fd90252daff284f0368352af90c40 100644 (file)
@@ -178,6 +178,11 @@ includes these key/value pairs in the goroutine status header of runtime
 tracebacks and debug=2 runtime/pprof stack dumps. This format may change in the future.
 (see go.dev/issue/76349)
 
+Go 1.26 added a new `cryptocustomrand` setting that controls whether most crypto/...
+APIs ignore the random `io.Reader` parameter. For Go 1.26, it defaults
+to `cryptocustomrand=0`, ignoring the random parameters. Using `cryptocustomrand=1`
+reverts to the pre-Go 1.26 behavior.
+
 ### Go 1.25
 
 Go 1.25 added a new `decoratemappings` setting that controls whether the Go
diff --git a/doc/next/6-stdlib/99-minor/crypto/dsa/70924.md b/doc/next/6-stdlib/99-minor/crypto/dsa/70924.md
new file mode 100644 (file)
index 0000000..0d99de8
--- /dev/null
@@ -0,0 +1,4 @@
+The random parameter to [GenerateKey] is now ignored.
+Instead, it now always uses a secure source of cryptographically random bytes.
+For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function.
+The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior.
diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md b/doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md
new file mode 100644 (file)
index 0000000..e70325c
--- /dev/null
@@ -0,0 +1,4 @@
+The random parameter to [Curve.GenerateKey] is now ignored.
+Instead, it now always uses a secure source of cryptographically random bytes.
+For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function.
+The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior.
diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md b/doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md
new file mode 100644 (file)
index 0000000..15344cb
--- /dev/null
@@ -0,0 +1,4 @@
+The random parameter to [GenerateKey], [SignASN1], [Sign], and [PrivateKey.Sign] is now ignored.
+Instead, they now always use a secure source of cryptographically random bytes.
+For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function.
+The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior.
diff --git a/doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md b/doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md
new file mode 100644 (file)
index 0000000..885e425
--- /dev/null
@@ -0,0 +1,4 @@
+If the random parameter to [GenerateKey] is nil, GenerateKey now always uses a
+secure source of cryptographically random bytes, instead of [crypto/rand.Reader]
+(which could have been overridden). The new GODEBUG setting `cryptocustomrand=1`
+temporarily restores the old behavior.
diff --git a/doc/next/6-stdlib/99-minor/crypto/rand/70924.md b/doc/next/6-stdlib/99-minor/crypto/rand/70924.md
new file mode 100644 (file)
index 0000000..dfdbaa3
--- /dev/null
@@ -0,0 +1,4 @@
+The random parameter to [Prime] is now ignored.
+Instead, it now always uses a secure source of cryptographically random bytes.
+For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function.
+The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior.
diff --git a/doc/next/6-stdlib/99-minor/crypto/rsa/70924.md b/doc/next/6-stdlib/99-minor/crypto/rsa/70924.md
new file mode 100644 (file)
index 0000000..195e3ef
--- /dev/null
@@ -0,0 +1,4 @@
+The random parameter to [GenerateKey], [GenerateMultiPrimeKey], and [EncryptPKCS1v15] is now ignored.
+Instead, they now always use a secure source of cryptographically random bytes.
+For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function.
+The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior.
diff --git a/doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md b/doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md
new file mode 100644 (file)
index 0000000..b8d5913
--- /dev/null
@@ -0,0 +1,4 @@
+The new [SetGlobalRandom] function configures a global, deterministic
+cryptographic randomness source for the duration of the test. It affects
+crypto/rand, and all implicit sources of cryptographic randomness in the
+`crypto/...` packages.
index ecc4c82bb524fd4acc47076d24b2aa51b8ba2ca6..6724f861b7f2f027a2a0481e6627cf3f269cb434 100644 (file)
@@ -19,7 +19,7 @@ import (
        "math/big"
 
        "crypto/internal/fips140only"
-       "crypto/internal/randutil"
+       "crypto/internal/rand"
 )
 
 // Parameters represents the domain parameters for a key. These parameters can
@@ -209,14 +209,18 @@ func fermatInverse(k, P *big.Int) *big.Int {
 // to the byte-length of the subgroup. This function does not perform that
 // truncation itself.
 //
+// Since Go 1.26, a secure source of random bytes is always used, and the Reader is
+// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed
+// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].
+//
 // Be aware that calling Sign with an attacker-controlled [PrivateKey] may
 // require an arbitrary amount of CPU.
-func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
+func Sign(random io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
        if fips140only.Enforced() {
                return nil, nil, errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode")
        }
 
-       randutil.MaybeReadByte(rand)
+       random = rand.CustomReader(random)
 
        // FIPS 186-3, section 4.6
 
@@ -232,7 +236,7 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
                k := new(big.Int)
                buf := make([]byte, n)
                for {
-                       _, err = io.ReadFull(rand, buf)
+                       _, err = io.ReadFull(random, buf)
                        if err != nil {
                                return
                        }
index 82daacf4736a69db49c6776a948ead071fbb094a..3f85a2833697a9fb0b5b9d2de21a5240cd3e8452 100644 (file)
@@ -18,9 +18,9 @@ import (
 type Curve interface {
        // GenerateKey generates a random PrivateKey.
        //
-       // Most applications should use [crypto/rand.Reader] as rand. Note that the
-       // returned key does not depend deterministically on the bytes read from rand,
-       // and may change between calls and/or between versions.
+       // Since Go 1.26, a secure source of random bytes is always used, and rand
+       // is ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be
+       // removed in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].
        GenerateKey(rand io.Reader) (*PrivateKey, error)
 
        // NewPrivateKey checks that key is valid and returns a PrivateKey.
index 0d58196842a7ae73cf0a4ba6940735c9fdb2e590..de7348d92323494e2ddc4fffc86aba121e777399 100644 (file)
@@ -9,6 +9,7 @@ import (
        "crypto/internal/boring"
        "crypto/internal/fips140/ecdh"
        "crypto/internal/fips140only"
+       "crypto/internal/rand"
        "errors"
        "io"
 )
@@ -25,8 +26,8 @@ func (c *nistCurve) String() string {
        return c.name
 }
 
-func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
-       if boring.Enabled && rand == boring.RandReader {
+func (c *nistCurve) GenerateKey(r io.Reader) (*PrivateKey, error) {
+       if boring.Enabled && r == boring.RandReader {
                key, bytes, err := boring.GenerateKeyECDH(c.name)
                if err != nil {
                        return nil, err
@@ -44,11 +45,13 @@ func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
                return k, nil
        }
 
-       if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) {
+       r = rand.CustomReader(r)
+
+       if fips140only.Enforced() && !fips140only.ApprovedRandomReader(r) {
                return nil, errors.New("crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-only mode")
        }
 
-       privateKey, err := c.generate(rand)
+       privateKey, err := c.generate(r)
        if err != nil {
                return nil, err
        }
index 3ad13f3e73a21cb7c455e98b155add9f5e32ea34..21a921aa12d44db2ea66ee1e431e0f20453f364c 100644 (file)
@@ -8,7 +8,7 @@ import (
        "bytes"
        "crypto/internal/fips140/edwards25519/field"
        "crypto/internal/fips140only"
-       "crypto/internal/randutil"
+       "crypto/internal/rand"
        "errors"
        "io"
 )
@@ -34,13 +34,13 @@ func (c *x25519Curve) String() string {
        return "X25519"
 }
 
-func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
+func (c *x25519Curve) GenerateKey(r io.Reader) (*PrivateKey, error) {
        if fips140only.Enforced() {
                return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
        }
+       r = rand.CustomReader(r)
        key := make([]byte, x25519PrivateKeySize)
-       randutil.MaybeReadByte(rand)
-       if _, err := io.ReadFull(rand, key); err != nil {
+       if _, err := io.ReadFull(r, key); err != nil {
                return nil, err
        }
        return c.NewPrivateKey(key)
index 9d965c4e7b8eebfd43c6d57e41506997823431cd..aee15b9283854385ab88cc67267ededa900ed430 100644 (file)
@@ -27,7 +27,7 @@ import (
        "crypto/internal/fips140cache"
        "crypto/internal/fips140hash"
        "crypto/internal/fips140only"
-       "crypto/internal/randutil"
+       "crypto/internal/rand"
        "crypto/sha512"
        "crypto/subtle"
        "errors"
@@ -310,31 +310,31 @@ func privateKeyBytes[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey) ([]b
 // the bit-length of the private key's curve order, the hash will be truncated
 // to that length. It returns the ASN.1 encoded signature, like [SignASN1].
 //
-// If rand is not nil, the signature is randomized. Most applications should use
-// [crypto/rand.Reader] as rand. Note that the returned signature does not
-// depend deterministically on the bytes read from rand, and may change between
-// calls and/or between versions.
+// If random is not nil, the signature is randomized. Most applications should use
+// [crypto/rand.Reader] as random, but unless GODEBUG=cryptocustomrand=1 is set, a
+// secure source of random bytes is always used, and the actual Reader is ignored.
+// The GODEBUG setting will be removed in a future Go release. Instead, use
+// [testing/cryptotest.SetGlobalRandom].
 //
-// If rand is nil, Sign will produce a deterministic signature according to RFC
+// If random is nil, Sign will produce a deterministic signature according to RFC
 // 6979. When producing a deterministic signature, opts.HashFunc() must be the
 // function used to produce digest and priv.Curve must be one of
 // [elliptic.P224], [elliptic.P256], [elliptic.P384], or [elliptic.P521].
-func (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
-       if rand == nil {
+func (priv *PrivateKey) Sign(random io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
+       if random == nil {
                return signRFC6979(priv, digest, opts)
        }
-       return SignASN1(rand, priv, digest)
+       random = rand.CustomReader(random)
+       return SignASN1(random, priv, digest)
 }
 
 // GenerateKey generates a new ECDSA private key for the specified curve.
 //
-// Most applications should use [crypto/rand.Reader] as rand. Note that the
-// returned key does not depend deterministically on the bytes read from rand,
-// and may change between calls and/or between versions.
-func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
-       randutil.MaybeReadByte(rand)
-
-       if boring.Enabled && rand == boring.RandReader {
+// Since Go 1.26, a secure source of random bytes is always used, and the Reader is
+// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed
+// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].
+func GenerateKey(c elliptic.Curve, r io.Reader) (*PrivateKey, error) {
+       if boring.Enabled && r == boring.RandReader {
                x, y, d, err := boring.GenerateKeyECDSA(c.Params().Name)
                if err != nil {
                        return nil, err
@@ -343,17 +343,19 @@ func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
        }
        boring.UnreachableExceptTests()
 
+       r = rand.CustomReader(r)
+
        switch c.Params() {
        case elliptic.P224().Params():
-               return generateFIPS(c, ecdsa.P224(), rand)
+               return generateFIPS(c, ecdsa.P224(), r)
        case elliptic.P256().Params():
-               return generateFIPS(c, ecdsa.P256(), rand)
+               return generateFIPS(c, ecdsa.P256(), r)
        case elliptic.P384().Params():
-               return generateFIPS(c, ecdsa.P384(), rand)
+               return generateFIPS(c, ecdsa.P384(), r)
        case elliptic.P521().Params():
-               return generateFIPS(c, ecdsa.P521(), rand)
+               return generateFIPS(c, ecdsa.P521(), r)
        default:
-               return generateLegacy(c, rand)
+               return generateLegacy(c, r)
        }
 }
 
@@ -373,13 +375,12 @@ func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], ran
 // private key's curve order, the hash will be truncated to that length. It
 // returns the ASN.1 encoded signature.
 //
-// The signature is randomized. Most applications should use [crypto/rand.Reader]
-// as rand. Note that the returned signature does not depend deterministically on
-// the bytes read from rand, and may change between calls and/or between versions.
-func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) {
-       randutil.MaybeReadByte(rand)
-
-       if boring.Enabled && rand == boring.RandReader {
+// The signature is randomized. Since Go 1.26, a secure source of random bytes
+// is always used, and the Reader is ignored unless GODEBUG=cryptocustomrand=1
+// is set. This setting will be removed in a future Go release. Instead, use
+// [testing/cryptotest.SetGlobalRandom].
+func SignASN1(r io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) {
+       if boring.Enabled && r == boring.RandReader {
                b, err := boringPrivateKey(priv)
                if err != nil {
                        return nil, err
@@ -388,17 +389,19 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) {
        }
        boring.UnreachableExceptTests()
 
+       r = rand.CustomReader(r)
+
        switch priv.Curve.Params() {
        case elliptic.P224().Params():
-               return signFIPS(ecdsa.P224(), priv, rand, hash)
+               return signFIPS(ecdsa.P224(), priv, r, hash)
        case elliptic.P256().Params():
-               return signFIPS(ecdsa.P256(), priv, rand, hash)
+               return signFIPS(ecdsa.P256(), priv, r, hash)
        case elliptic.P384().Params():
-               return signFIPS(ecdsa.P384(), priv, rand, hash)
+               return signFIPS(ecdsa.P384(), priv, r, hash)
        case elliptic.P521().Params():
-               return signFIPS(ecdsa.P521(), priv, rand, hash)
+               return signFIPS(ecdsa.P521(), priv, r, hash)
        default:
-               return signLegacy(priv, rand, hash)
+               return signLegacy(priv, r, hash)
        }
 }
 
index f6b4401bd758f96a69ba0edd024e165c98fb257f..2fb1b21a6059867e0e275ea80953e17a16712f90 100644 (file)
@@ -61,6 +61,11 @@ var errZeroParam = errors.New("zero parameter")
 // private key's curve order, the hash will be truncated to that length. It
 // returns the signature as a pair of integers. Most applications should use
 // [SignASN1] instead of dealing directly with r, s.
+//
+// The signature is randomized. Since Go 1.26, a secure source of random bytes
+// is always used, and the Reader is ignored unless GODEBUG=cryptocustomrand=1
+// is set. This setting will be removed in a future Go release. Instead, use
+// [testing/cryptotest.SetGlobalRandom].
 func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
        sig, err := SignASN1(rand, priv, hash)
        if err != nil {
index 26b4882b13280e8521407b33453899ac07e67678..f09dabe23e875dd6ba9d7dcc34daf22eb844d65a 100644 (file)
@@ -17,12 +17,15 @@ package ed25519
 
 import (
        "crypto"
+       "crypto/internal/fips140/drbg"
        "crypto/internal/fips140/ed25519"
        "crypto/internal/fips140cache"
        "crypto/internal/fips140only"
+       "crypto/internal/rand"
        cryptorand "crypto/rand"
        "crypto/subtle"
        "errors"
+       "internal/godebug"
        "io"
        "strconv"
 )
@@ -135,18 +138,31 @@ type Options struct {
 // HashFunc returns o.Hash.
 func (o *Options) HashFunc() crypto.Hash { return o.Hash }
 
-// GenerateKey generates a public/private key pair using entropy from rand.
-// If rand is nil, [crypto/rand.Reader] will be used.
+var cryptocustomrand = godebug.New("cryptocustomrand")
+
+// GenerateKey generates a public/private key pair using entropy from random.
+//
+// If random is nil, a secure random source is used. (Before Go 1.26, a custom
+// [crypto/rand.Reader] was used if set by the application. That behavior can be
+// restored with GODEBUG=cryptocustomrand=1. This setting will be removed in a
+// future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].)
 //
 // The output of this function is deterministic, and equivalent to reading
-// [SeedSize] bytes from rand, and passing them to [NewKeyFromSeed].
-func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
-       if rand == nil {
-               rand = cryptorand.Reader
+// [SeedSize] bytes from random, and passing them to [NewKeyFromSeed].
+func GenerateKey(random io.Reader) (PublicKey, PrivateKey, error) {
+       if random == nil {
+               if cryptocustomrand.Value() == "1" {
+                       random = cryptorand.Reader
+                       if _, ok := random.(drbg.DefaultReader); !ok {
+                               cryptocustomrand.IncNonDefault()
+                       }
+               } else {
+                       random = rand.Reader
+               }
        }
 
        seed := make([]byte, SeedSize)
-       if _, err := io.ReadFull(rand, seed); err != nil {
+       if _, err := io.ReadFull(random, seed); err != nil {
                return nil, nil, err
        }
 
index c30f79bad494bf5e0b22155d675774a7039e3533..7633aa2b714ca9ae46f7e1136a07776530538526 100644 (file)
@@ -6,7 +6,7 @@ package hpke
 
 import (
        "crypto/ecdh"
-       "crypto/rand"
+       "crypto/internal/rand"
        "errors"
        "internal/byteorder"
 )
index 322f937ae8608b9a35f60f09ccb10dcedaacd380..a79dadf58f310cb75f253102ddcd3d798506cef7 100644 (file)
@@ -8,8 +8,9 @@ import (
        "bytes"
        "crypto"
        "crypto/ecdh"
+       "crypto/internal/fips140/drbg"
+       "crypto/internal/rand"
        "crypto/mlkem"
-       "crypto/rand"
        "crypto/sha3"
        "errors"
        "internal/byteorder"
@@ -246,7 +247,7 @@ func NewHybridPrivateKey(pq crypto.Decapsulator, t ecdh.KeyExchanger) (PrivateKe
 
 func (kem *hybridKEM) GenerateKey() (PrivateKey, error) {
        seed := make([]byte, 32)
-       rand.Read(seed)
+       drbg.Read(seed)
        return kem.NewPrivateKey(seed)
 }
 
index cec697c7ab89ec90c6dfdc4eef65798210b3798b..949e74ac60c51bcd7a623c7433911c8d9de33c67 100644 (file)
@@ -11,7 +11,6 @@ package drbg
 import (
        entropy "crypto/internal/entropy/v1.0.0"
        "crypto/internal/fips140"
-       "crypto/internal/randutil"
        "crypto/internal/sysrand"
        "io"
        "sync"
@@ -63,6 +62,15 @@ var drbgPool = sync.Pool{
 // uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG).
 // Otherwise, it uses the operating system's random number generator.
 func Read(b []byte) {
+       if testingReader != nil {
+               fips140.RecordNonApproved()
+               // Avoid letting b escape in the non-testing case.
+               bb := make([]byte, len(b))
+               testingReader.Read(bb)
+               copy(b, bb)
+               return
+       }
+
        if !fips140.Enabled {
                sysrand.Read(b)
                return
@@ -101,36 +109,33 @@ func Read(b []byte) {
        }
 }
 
+var testingReader io.Reader
+
+// SetTestingReader sets a global, deterministic cryptographic randomness source
+// for testing purposes. Its Read method must never return an error, it must
+// never return short, and it must be safe for concurrent use.
+//
+// This is only intended to be used by the testing/cryptotest package.
+func SetTestingReader(r io.Reader) {
+       testingReader = r
+}
+
 // DefaultReader is a sentinel type, embedded in the default
 // [crypto/rand.Reader], used to recognize it when passed to
 // APIs that accept a rand io.Reader.
+//
+// Any Reader that implements this interface is assumed to
+// call [Read] as its Read method.
 type DefaultReader interface{ defaultReader() }
 
 // ReadWithReader uses Reader to fill b with cryptographically secure random
 // bytes. It is intended for use in APIs that expose a rand io.Reader.
-//
-// If Reader is not the default Reader from crypto/rand,
-// [randutil.MaybeReadByte] and [fips140.RecordNonApproved] are called.
 func ReadWithReader(r io.Reader, b []byte) error {
        if _, ok := r.(DefaultReader); ok {
                Read(b)
                return nil
        }
 
-       fips140.RecordNonApproved()
-       randutil.MaybeReadByte(r)
-       _, err := io.ReadFull(r, b)
-       return err
-}
-
-// ReadWithReaderDeterministic is like ReadWithReader, but it doesn't call
-// [randutil.MaybeReadByte] on non-default Readers.
-func ReadWithReaderDeterministic(r io.Reader, b []byte) error {
-       if _, ok := r.(DefaultReader); ok {
-               Read(b)
-               return nil
-       }
-
        fips140.RecordNonApproved()
        _, err := io.ReadFull(r, b)
        return err
index 29c47069a3e0ee0f35cfde316c0eeb674ed787e2..4a043213fd03d7496fd71ef04e51938a57759a84 100644 (file)
@@ -272,8 +272,8 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash hash.Hash, hashed []byte, sa
        checkApprovedHash(hash)
 
        // Note that while we don't commit to deterministic execution with respect
-       // to the rand stream, we also don't apply MaybeReadByte, so per Hyrum's Law
-       // it's probably relied upon by some. It's a tolerable promise because a
+       // to the rand stream, we also never applied MaybeReadByte, so per Hyrum's
+       // Law it's probably relied upon by some. It's a tolerable promise because a
        // well-specified number of random bytes is included in the signature, in a
        // well-specified way.
 
@@ -286,7 +286,7 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash hash.Hash, hashed []byte, sa
                fips140.RecordNonApproved()
        }
        salt := make([]byte, saltLength)
-       if err := drbg.ReadWithReaderDeterministic(rand, salt); err != nil {
+       if err := drbg.ReadWithReader(rand, salt); err != nil {
                return nil, err
        }
 
@@ -372,7 +372,7 @@ func checkApprovedHash(hash hash.Hash) {
 // EncryptOAEP encrypts the given message with RSAES-OAEP.
 func EncryptOAEP(hash, mgfHash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) {
        // Note that while we don't commit to deterministic execution with respect
-       // to the random stream, we also don't apply MaybeReadByte, so per Hyrum's
+       // to the random stream, we also never applied MaybeReadByte, so per Hyrum's
        // Law it's probably relied upon by some. It's a tolerable promise because a
        // well-specified number of random bytes is included in the ciphertext, in a
        // well-specified way.
@@ -402,7 +402,7 @@ func EncryptOAEP(hash, mgfHash hash.Hash, random io.Reader, pub *PublicKey, msg
        db[len(db)-len(msg)-1] = 1
        copy(db[len(db)-len(msg):], msg)
 
-       if err := drbg.ReadWithReaderDeterministic(random, seed); err != nil {
+       if err := drbg.ReadWithReader(random, seed); err != nil {
                return nil, err
        }
 
diff --git a/src/crypto/internal/rand/rand.go b/src/crypto/internal/rand/rand.go
new file mode 100644 (file)
index 0000000..3a78095
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand
+
+import (
+       "crypto/internal/boring"
+       "crypto/internal/fips140/drbg"
+       "crypto/internal/randutil"
+       "internal/godebug"
+       "io"
+       _ "unsafe"
+)
+
+type reader struct {
+       drbg.DefaultReader
+}
+
+func (r reader) Read(b []byte) (n int, err error) {
+       if boring.Enabled {
+               if _, err := boring.RandReader.Read(b); err != nil {
+                       panic("crypto/rand: boring RandReader failed: " + err.Error())
+               }
+               return len(b), nil
+       }
+       drbg.Read(b)
+       return len(b), nil
+}
+
+// Reader is an io.Reader that calls [drbg.Read].
+//
+// It should be used internally instead of [crypto/rand.Reader], because the
+// latter can be set by applications outside of tests. These applications then
+// risk breaking between Go releases, if the way the Reader is used changes.
+var Reader io.Reader = reader{}
+
+// SetTestingReader overrides all calls to [drbg.Read]. The Read method of
+// r must never return an error or return short.
+//
+// SetTestingReader panics when building against Go Cryptographic Module v1.0.0.
+//
+// SetTestingReader is pulled by [testing/cryptotest.setGlobalRandom] via go:linkname.
+//
+//go:linkname SetTestingReader crypto/internal/rand.SetTestingReader
+func SetTestingReader(r io.Reader) {
+       fips140SetTestingReader(r)
+}
+
+var cryptocustomrand = godebug.New("cryptocustomrand")
+
+// CustomReader returns [Reader] or, only if the GODEBUG setting
+// "cryptocustomrand=1" is set, the provided io.Reader.
+//
+// If returning a non-default Reader, it calls [randutil.MaybeReadByte] on it.
+func CustomReader(r io.Reader) io.Reader {
+       if cryptocustomrand.Value() == "1" {
+               if _, ok := r.(drbg.DefaultReader); !ok {
+                       randutil.MaybeReadByte(r)
+                       cryptocustomrand.IncNonDefault()
+               }
+               return r
+       }
+       return Reader
+}
diff --git a/src/crypto/internal/rand/rand_fipsv1.0.go b/src/crypto/internal/rand/rand_fipsv1.0.go
new file mode 100644 (file)
index 0000000..29eba7e
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build fips140v1.0
+
+package rand
+
+import "io"
+
+func fips140SetTestingReader(r io.Reader) {
+       panic("cryptotest.SetGlobalRandom is not supported when building against Go Cryptographic Module v1.0.0")
+}
diff --git a/src/crypto/internal/rand/rand_fipsv2.0.go b/src/crypto/internal/rand/rand_fipsv2.0.go
new file mode 100644 (file)
index 0000000..0dc18e7
--- /dev/null
@@ -0,0 +1,16 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !fips140v1.0
+
+package rand
+
+import (
+       "crypto/internal/fips140/drbg"
+       "io"
+)
+
+func fips140SetTestingReader(r io.Reader) {
+       drbg.SetTestingReader(r)
+}
index 034bf617155a6f3c4d82080d0894153366288dbb..5f3977aaddfe9c68334a4f93738e2ab7b4e429c3 100644 (file)
@@ -31,6 +31,9 @@ var testingOnlyFailRead bool
 // system. It always fills b entirely and crashes the program irrecoverably if
 // an error is encountered. The operating system APIs are documented to never
 // return an error on all but legacy Linux systems.
+//
+// Note that Read is not affected by [testing/cryptotest.SetGlobalRand], and it
+// should not be used directly by algorithm implementations.
 func Read(b []byte) {
        if firstUse.CompareAndSwap(false, true) {
                // First use of randomness. Start timer to warn about
index 176b79673b08512287cc80efc3cd73e7ed888e18..b652e3bae9dd717afdcfe5894148d6e9c79a3f6c 100644 (file)
@@ -43,7 +43,7 @@ type DecapsulationKey768 struct {
 }
 
 // GenerateKey768 generates a new decapsulation key, drawing random bytes from
-// the default crypto/rand source. The decapsulation key must be kept secret.
+// a secure source. The decapsulation key must be kept secret.
 func GenerateKey768() (*DecapsulationKey768, error) {
        key, err := mlkem.GenerateKey768()
        if err != nil {
@@ -118,7 +118,7 @@ func (ek *EncapsulationKey768) Bytes() []byte {
 }
 
 // Encapsulate generates a shared key and an associated ciphertext from an
-// encapsulation key, drawing random bytes from the default crypto/rand source.
+// encapsulation key, drawing random bytes from a secure source.
 //
 // The shared key must be kept secret.
 //
@@ -135,7 +135,7 @@ type DecapsulationKey1024 struct {
 }
 
 // GenerateKey1024 generates a new decapsulation key, drawing random bytes from
-// the default crypto/rand source. The decapsulation key must be kept secret.
+// a secure source. The decapsulation key must be kept secret.
 func GenerateKey1024() (*DecapsulationKey1024, error) {
        key, err := mlkem.GenerateKey1024()
        if err != nil {
@@ -210,7 +210,7 @@ func (ek *EncapsulationKey1024) Bytes() []byte {
 }
 
 // Encapsulate generates a shared key and an associated ciphertext from an
-// encapsulation key, drawing random bytes from the default crypto/rand source.
+// encapsulation key, drawing random bytes from a secure source.
 //
 // The shared key must be kept secret.
 //
index 1ca16caa9563e68c5684256cbd92c0ea6d16ac6b..004e6b6fedc7fc4e696cba7f546fc0732bcbdcd9 100644 (file)
@@ -8,11 +8,14 @@ package rand
 
 import (
        "crypto/internal/boring"
-       "crypto/internal/fips140"
        "crypto/internal/fips140/drbg"
-       "crypto/internal/sysrand"
+       "crypto/internal/rand"
        "io"
        _ "unsafe"
+
+       // Ensure the go:linkname from testing/cryptotest to
+       // crypto/internal/rand.SetTestingReader works.
+       _ "crypto/internal/rand"
 )
 
 // Reader is a global, shared instance of a cryptographically
@@ -35,21 +38,7 @@ func init() {
                Reader = boring.RandReader
                return
        }
-       Reader = &reader{}
-}
-
-type reader struct {
-       drbg.DefaultReader
-}
-
-func (r *reader) Read(b []byte) (n int, err error) {
-       boring.Unreachable()
-       if fips140.Enabled {
-               drbg.Read(b)
-       } else {
-               sysrand.Read(b)
-       }
-       return len(b), nil
+       Reader = rand.Reader
 }
 
 // fatal is [runtime.fatal], pushed via linkname.
@@ -68,8 +57,9 @@ func Read(b []byte) (n int, err error) {
        // through a potentially overridden Reader, so we special-case the default
        // case which we can keep non-escaping, and in the general case we read into
        // a heap buffer and copy from it.
-       if r, ok := Reader.(*reader); ok {
-               _, err = r.Read(b)
+       if _, ok := Reader.(drbg.DefaultReader); ok {
+               boring.Unreachable()
+               drbg.Read(b)
        } else {
                bb := make([]byte, len(b))
                _, err = io.ReadFull(Reader, bb)
index 8c9285197511ce65b15fe302dc6234daae148ec5..7cb9b47b4a658115500daee6658a0646c1b6ba74 100644 (file)
@@ -6,7 +6,7 @@ package rand
 
 import (
        "crypto/internal/fips140only"
-       "crypto/internal/randutil"
+       "crypto/internal/rand"
        "errors"
        "io"
        "math/big"
@@ -14,7 +14,11 @@ import (
 
 // Prime returns a number of the given bit length that is prime with high probability.
 // Prime will return error for any error returned by rand.Read or if bits < 2.
-func Prime(rand io.Reader, bits int) (*big.Int, error) {
+//
+// Since Go 1.26, a secure source of random bytes is always used, and the Reader is
+// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed
+// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].
+func Prime(r io.Reader, bits int) (*big.Int, error) {
        if fips140only.Enforced() {
                return nil, errors.New("crypto/rand: use of Prime is not allowed in FIPS 140-only mode")
        }
@@ -22,7 +26,7 @@ func Prime(rand io.Reader, bits int) (*big.Int, error) {
                return nil, errors.New("crypto/rand: prime size must be at least 2-bit")
        }
 
-       randutil.MaybeReadByte(rand)
+       r = rand.CustomReader(r)
 
        b := uint(bits % 8)
        if b == 0 {
@@ -33,7 +37,7 @@ func Prime(rand io.Reader, bits int) (*big.Int, error) {
        p := new(big.Int)
 
        for {
-               if _, err := io.ReadFull(rand, bytes); err != nil {
+               if _, err := io.ReadFull(r, bytes); err != nil {
                        return nil, err
                }
 
index caf68957e2d35a95dc54d29f4eae106702e560e0..0f216e0193252c6794ec7b3d39d90997cb11ba20 100644 (file)
@@ -8,7 +8,7 @@ import (
        "crypto/internal/boring"
        "crypto/internal/fips140/rsa"
        "crypto/internal/fips140only"
-       "crypto/internal/randutil"
+       "crypto/internal/rand"
        "crypto/subtle"
        "errors"
        "io"
@@ -36,12 +36,11 @@ type PKCS1v15DecryptOptions struct {
 // scheme from PKCS #1 v1.5.  The message must be no longer than the
 // length of the public modulus minus 11 bytes.
 //
-// The random parameter is used as a source of entropy to ensure that
-// encrypting the same message twice doesn't result in the same
-// ciphertext. Most applications should use [crypto/rand.Reader]
-// as random. Note that the returned ciphertext does not depend
-// deterministically on the bytes read from random, and may change
-// between calls and/or between versions.
+// The random parameter is used as a source of entropy to ensure that encrypting
+// the same message twice doesn't result in the same ciphertext. Since Go 1.26,
+// a secure source of random bytes is always used, and the Reader is ignored
+// unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed in a
+// future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].
 //
 // Deprecated: PKCS #1 v1.5 encryption is dangerous and should not be used.
 // See [draft-irtf-cfrg-rsa-guidance-05] for more information. Use
@@ -57,8 +56,6 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro
                return nil, err
        }
 
-       randutil.MaybeReadByte(random)
-
        k := pub.Size()
        if len(msg) > k-11 {
                return nil, ErrMessageTooLong
@@ -73,6 +70,8 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro
        }
        boring.UnreachableExceptTests()
 
+       random = rand.CustomReader(random)
+
        // EM = 0x00 || 0x02 || PS || 0x00 || M
        em := make([]byte, k)
        em[1] = 2
index 5680d8b54159dbdccacbed881bb2dc8d140121fd..62f2de30b0b8711a9a3bf7a59337a2ba1024ba31 100644 (file)
@@ -48,8 +48,8 @@ import (
        "crypto/internal/fips140/bigmod"
        "crypto/internal/fips140/rsa"
        "crypto/internal/fips140only"
-       "crypto/internal/randutil"
-       "crypto/rand"
+       "crypto/internal/rand"
+       cryptorand "crypto/rand"
        "crypto/subtle"
        "errors"
        "fmt"
@@ -304,9 +304,9 @@ func checkPublicKeySize(k *PublicKey) error {
 // If bits is less than 1024, [GenerateKey] returns an error. See the "[Minimum
 // key size]" section for further details.
 //
-// Most applications should use [crypto/rand.Reader] as rand. Note that the
-// returned key does not depend deterministically on the bytes read from rand,
-// and may change between calls and/or between versions.
+// Since Go 1.26, a secure source of random bytes is always used, and the Reader is
+// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed
+// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].
 //
 // [Minimum key size]: https://pkg.go.dev/crypto/rsa#hdr-Minimum_key_size
 func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) {
@@ -350,6 +350,8 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) {
                return key, nil
        }
 
+       random = rand.CustomReader(random)
+
        if fips140only.Enforced() && bits < 2048 {
                return nil, errors.New("crypto/rsa: use of keys smaller than 2048 bits is not allowed in FIPS 140-only mode")
        }
@@ -415,6 +417,10 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) {
 // This package does not implement CRT optimizations for multi-prime RSA, so the
 // keys with more than two primes will have worse performance.
 //
+// Since Go 1.26, a secure source of random bytes is always used, and the Reader is
+// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed
+// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].
+//
 // Deprecated: The use of this function with a number of primes different from
 // two is not recommended for the above security, compatibility, and performance
 // reasons. Use [GenerateKey] instead.
@@ -428,7 +434,7 @@ func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey
                return nil, errors.New("crypto/rsa: multi-prime RSA is not allowed in FIPS 140-only mode")
        }
 
-       randutil.MaybeReadByte(random)
+       random = rand.CustomReader(random)
 
        priv := new(PrivateKey)
        priv.E = 65537
@@ -473,7 +479,7 @@ NextSetOfPrimes:
                }
                for i := 0; i < nprimes; i++ {
                        var err error
-                       primes[i], err = rand.Prime(random, todo/(nprimes-i))
+                       primes[i], err = cryptorand.Prime(random, todo/(nprimes-i))
                        if err != nil {
                                return nil, err
                        }
index 3e2c5663087828da4d4b4e89424000f7aa058232..6e15459a9a180936f3f9d1700fc3c04fc97bd89c 100644 (file)
@@ -448,6 +448,10 @@ func runMain(m *testing.M) int {
                os.Exit(1)
        }
 
+       // TODO(filippo): deprecate Config.Rand, and regenerate handshake recordings
+       // to use cryptotest.SetGlobalRandom instead.
+       os.Setenv("GODEBUG", "cryptocustomrand=1,"+os.Getenv("GODEBUG"))
+
        testConfig = &Config{
                Time:               func() time.Time { return time.Unix(0, 0) },
                Rand:               zeroSource{},
index e329c8a172725c8c8f89217fb94f473e7163e7b8..d58bd294cd103c116a2c436617be4357a858e5ca 100644 (file)
@@ -560,6 +560,7 @@ var depsRules = `
        < crypto/cipher
        < crypto/internal/boring
        < crypto/boring
+       < crypto/internal/rand
        < crypto/aes,
          crypto/des,
          crypto/rc4,
@@ -713,6 +714,9 @@ var depsRules = `
        log/slog, testing
        < testing/slogtest;
 
+       testing, crypto/rand
+       < testing/cryptotest;
+
        FMT, crypto/sha256, encoding/binary, encoding/json,
        go/ast, go/parser, go/token,
        internal/godebug, math/rand, encoding/hex
index f707fc34f2feda7971c7a6ff9f6b82c51ba8dd13..8f6d8bbdda656c853f7b5ef36f8e7ddb292e10ad 100644 (file)
@@ -29,6 +29,7 @@ var All = []Info{
        {Name: "allowmultiplevcs", Package: "cmd/go"},
        {Name: "asynctimerchan", Package: "time", Changed: 23, Old: "1"},
        {Name: "containermaxprocs", Package: "runtime", Changed: 25, Old: "0"},
+       {Name: "cryptocustomrand", Package: "crypto", Changed: 26, Old: "1"},
        {Name: "dataindependenttiming", Package: "crypto/subtle", Opaque: true},
        {Name: "decoratemappings", Package: "runtime", Opaque: true, Changed: 25, Old: "0"},
        {Name: "embedfollowsymlinks", Package: "cmd/go"},
index 8f908f5b520f730bdc8d936ae1507b8642503320..ca032f51b13533bae06f284ea5916759ce0e5199 100644 (file)
@@ -271,6 +271,11 @@ Below is the full list of supported metrics, ordered lexicographically.
                package due to a non-default GODEBUG=containermaxprocs=...
                setting.
 
+       /godebug/non-default-behavior/cryptocustomrand:events
+               The number of non-default behaviors executed by the crypto
+               package due to a non-default GODEBUG=cryptocustomrand=...
+               setting.
+
        /godebug/non-default-behavior/embedfollowsymlinks:events
                The number of non-default behaviors executed by the cmd/go
                package due to a non-default GODEBUG=embedfollowsymlinks=...
diff --git a/src/testing/cryptotest/rand.go b/src/testing/cryptotest/rand.go
new file mode 100644 (file)
index 0000000..d00732d
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package cryptotest provides deterministic random source testing.
+package cryptotest
+
+import (
+       cryptorand "crypto/rand"
+       "internal/byteorder"
+       "io"
+       mathrand "math/rand/v2"
+       "sync"
+       "testing"
+
+       // Import unsafe and crypto/rand, which imports crypto/internal/rand,
+       // for the crypto/internal/rand.SetTestingReader go:linkname.
+       _ "crypto/rand"
+       _ "unsafe"
+)
+
+//go:linkname randSetTestingReader crypto/internal/rand.SetTestingReader
+func randSetTestingReader(r io.Reader)
+
+//go:linkname testingCheckParallel testing.checkParallel
+func testingCheckParallel(t *testing.T)
+
+// SetGlobalRandom sets a global, deterministic cryptographic randomness source
+// for the duration of test t. It affects crypto/rand, and all implicit sources
+// of cryptographic randomness in the crypto/... packages.
+//
+// SetGlobalRandom may be called multiple times in the same test to reset the
+// random stream or change the seed.
+//
+// Because SetGlobalRandom affects the whole process, it cannot be used in
+// parallel tests or tests with parallel ancestors.
+//
+// Note that the way cryptographic algorithms use randomness is generally not
+// specified and may change over time. Thus, if a test expects a specific output
+// from a cryptographic function, it may fail in the future even if it uses
+// SetGlobalRandom.
+//
+// SetGlobalRandom is not supported when building against the Go Cryptographic
+// Module v1.0.0 (i.e. when [crypto/fips140.Version] returns "v1.0.0").
+func SetGlobalRandom(t *testing.T, seed uint64) {
+       if t == nil {
+               panic("cryptotest: SetGlobalRandom called with a nil *testing.T")
+       }
+       if !testing.Testing() {
+               panic("cryptotest: SetGlobalRandom used in a non-test binary")
+       }
+       testingCheckParallel(t)
+
+       var s [32]byte
+       byteorder.LEPutUint64(s[:8], seed)
+       r := &lockedReader{r: mathrand.NewChaCha8(s)}
+
+       randSetTestingReader(r)
+       previous := cryptorand.Reader
+       cryptorand.Reader = r
+       t.Cleanup(func() {
+               cryptorand.Reader = previous
+               randSetTestingReader(nil)
+       })
+}
+
+type lockedReader struct {
+       sync.Mutex
+       r *mathrand.ChaCha8
+}
+
+func (lr *lockedReader) Read(b []byte) (n int, err error) {
+       lr.Lock()
+       defer lr.Unlock()
+       return lr.r.Read(b)
+}
diff --git a/src/testing/cryptotest/rand_test.go b/src/testing/cryptotest/rand_test.go
new file mode 100644 (file)
index 0000000..bf18c1b
--- /dev/null
@@ -0,0 +1,202 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !fips140v1.0
+
+package cryptotest
+
+import (
+       "bytes"
+       "crypto/ecdsa"
+       "crypto/elliptic"
+       "crypto/mlkem"
+       "crypto/rand"
+       "encoding/hex"
+       "testing"
+)
+
+func TestSetGlobalRandom(t *testing.T) {
+       seed1, _ := hex.DecodeString("6ae6783f4fbde91b6eb88b73a48ed247dbe5882e2579683432c1bfc525454add" +
+               "0cd87274d67084caaf0e0d36c8496db7fef55fe0e125750aa608d5e20ffc2d12")
+
+       t.Run("rand.Read", func(t *testing.T) {
+               buf := make([]byte, 64)
+
+               t.Run("seed 1", func(t *testing.T) {
+                       SetGlobalRandom(t, 1)
+                       rand.Read(buf)
+                       if !bytes.Equal(buf, seed1) {
+                               t.Errorf("rand.Read with seed 1 = %x; want %x", buf, seed1)
+                       }
+
+                       rand.Read(buf)
+                       if bytes.Equal(buf, seed1) {
+                               t.Errorf("rand.Read with seed 1 returned same output twice: %x", buf)
+                       }
+
+                       SetGlobalRandom(t, 1)
+                       rand.Read(buf)
+                       if !bytes.Equal(buf, seed1) {
+                               t.Errorf("rand.Read with seed 1 after reset = %x; want %x", buf, seed1)
+                       }
+
+                       SetGlobalRandom(t, 1)
+               })
+
+               rand.Read(buf)
+               if bytes.Equal(buf, seed1) {
+                       t.Errorf("rand.Read returned seeded output after test end")
+               }
+
+               t.Run("seed 2", func(t *testing.T) {
+                       SetGlobalRandom(t, 2)
+                       rand.Read(buf)
+                       if bytes.Equal(buf, seed1) {
+                               t.Errorf("rand.Read with seed 2 = %x; want different from %x", buf, seed1)
+                       }
+               })
+       })
+
+       t.Run("rand.Reader", func(t *testing.T) {
+               buf := make([]byte, 64)
+
+               t.Run("seed 1", func(t *testing.T) {
+                       SetGlobalRandom(t, 1)
+                       rand.Reader.Read(buf)
+                       if !bytes.Equal(buf, seed1) {
+                               t.Errorf("rand.Reader.Read with seed 1 = %x; want %x", buf, seed1)
+                       }
+
+                       SetGlobalRandom(t, 1)
+               })
+
+               rand.Reader.Read(buf)
+               if bytes.Equal(buf, seed1) {
+                       t.Errorf("rand.Reader.Read returned seeded output after test end")
+               }
+
+               oldReader := rand.Reader
+               t.Cleanup(func() { rand.Reader = oldReader })
+               rand.Reader = bytes.NewReader(bytes.Repeat([]byte{5}, 64))
+
+               t.Run("seed 1 again", func(t *testing.T) {
+                       SetGlobalRandom(t, 1)
+                       rand.Reader.Read(buf)
+                       if !bytes.Equal(buf, seed1) {
+                               t.Errorf("rand.Reader.Read with seed 1 = %x; want %x", buf, seed1)
+                       }
+               })
+
+               rand.Reader.Read(buf)
+               if !bytes.Equal(buf, bytes.Repeat([]byte{5}, 64)) {
+                       t.Errorf("rand.Reader not restored")
+               }
+       })
+
+       // A direct internal use of drbg.Read.
+       t.Run("mlkem.GenerateKey768", func(t *testing.T) {
+               exp, err := mlkem.NewDecapsulationKey768(seed1)
+               if err != nil {
+                       t.Fatalf("mlkem.NewDecapsulationKey768: %v", err)
+               }
+
+               SetGlobalRandom(t, 1)
+               got, err := mlkem.GenerateKey768()
+               if err != nil {
+                       t.Fatalf("mlkem.GenerateKey768: %v", err)
+               }
+
+               if gotBytes := got.Bytes(); !bytes.Equal(gotBytes, exp.Bytes()) {
+                       t.Errorf("mlkem.GenerateKey768 with seed 1 = %x; want %x", gotBytes, exp.Bytes())
+               }
+       })
+
+       // An ignored passed-in Reader.
+       t.Run("ecdsa.GenerateKey", func(t *testing.T) {
+               exp, err := ecdsa.ParseRawPrivateKey(elliptic.P384(), seed1[:48])
+               if err != nil {
+                       t.Fatalf("ecdsa.ParseRawPrivateKey: %v", err)
+               }
+
+               SetGlobalRandom(t, 1)
+               got, err := ecdsa.GenerateKey(elliptic.P384(), bytes.NewReader([]byte("this reader is ignored")))
+               if err != nil {
+                       t.Fatalf("ecdsa.GenerateKey: %v", err)
+               }
+
+               if !got.Equal(exp) {
+                       t.Errorf("ecdsa.GenerateKey with seed 1 = %x; want %x", got.D.Bytes(), exp.D.Bytes())
+               }
+       })
+
+       // The passed-in Reader is used if cryptocustomrand=1 is set,
+       // and MaybeReadByte is called on it.
+       t.Run("cryptocustomrand=1", func(t *testing.T) {
+               t.Setenv("GODEBUG", "cryptocustomrand=1")
+
+               buf := make([]byte, 49)
+               buf[0] = 42
+               for i := 2; i < 49; i++ {
+                       buf[i] = 1
+               }
+
+               exp1, err := ecdsa.ParseRawPrivateKey(elliptic.P384(), buf[:48])
+               if err != nil {
+                       t.Fatalf("ecdsa.ParseRawPrivateKey: %v", err)
+               }
+               exp2, err := ecdsa.ParseRawPrivateKey(elliptic.P384(), buf[1:49])
+               if err != nil {
+                       t.Fatalf("ecdsa.ParseRawPrivateKey: %v", err)
+               }
+
+               seen := [2]bool{}
+               for i := 0; i < 1000; i++ {
+                       r := bytes.NewReader(buf)
+                       got, err := ecdsa.GenerateKey(elliptic.P384(), r)
+                       if err != nil {
+                               t.Fatalf("ecdsa.GenerateKey: %v", err)
+                       }
+                       switch {
+                       case got.Equal(exp1):
+                               seen[0] = true
+                       case got.Equal(exp2):
+                               seen[1] = true
+                       default:
+                               t.Fatalf("ecdsa.GenerateKey with custom reader = %x; want %x or %x", got.D.Bytes(), exp1.D.Bytes(), exp2.D.Bytes())
+                       }
+                       if seen[0] && seen[1] {
+                               break
+                       }
+               }
+               if !seen[0] || !seen[1] {
+                       t.Errorf("ecdsa.GenerateKey with custom reader did not produce both expected keys")
+               }
+
+               // Again, with SetGlobalRandom.
+               SetGlobalRandom(t, 1)
+
+               seen = [2]bool{}
+               for i := 0; i < 1000; i++ {
+                       r := bytes.NewReader(buf)
+                       got, err := ecdsa.GenerateKey(elliptic.P384(), r)
+                       if err != nil {
+                               t.Fatalf("ecdsa.GenerateKey: %v", err)
+                       }
+                       switch {
+                       case got.Equal(exp1):
+                               seen[0] = true
+                       case got.Equal(exp2):
+                               seen[1] = true
+                       default:
+                               t.Fatalf("ecdsa.GenerateKey with custom reader and SetGlobalRandom = %x; want %x or %x", got.D.Bytes(), exp1.D.Bytes(), exp2.D.Bytes())
+                       }
+                       if seen[0] && seen[1] {
+                               break
+                       }
+               }
+               if !seen[0] || !seen[1] {
+                       t.Errorf("ecdsa.GenerateKey with custom reader and SetGlobalRandom did not produce both expected keys")
+               }
+       })
+}
index 0d1d08ca89a5e635d5af04f72edf89cc50c0526d..34b45b41b9addfee91d11f1e54266cdfc1827e11 100644 (file)
@@ -1749,7 +1749,7 @@ func pcToName(pc uintptr) string {
        return frame.Function
 }
 
-const parallelConflict = `testing: test using t.Setenv or t.Chdir can not use t.Parallel`
+const parallelConflict = `testing: test using t.Setenv, t.Chdir, or cryptotest.SetGlobalRandom can not use t.Parallel`
 
 // Parallel signals that this test is to be run in parallel with (and only with)
 // other parallel tests. When a test is run multiple times due to use of
@@ -1820,6 +1820,13 @@ func (t *T) Parallel() {
        t.lastRaceErrors.Store(int64(race.Errors()))
 }
 
+// checkParallel is called by [testing/cryptotest.SetGlobalRandom].
+//
+//go:linkname checkParallel testing.checkParallel
+func checkParallel(t *T) {
+       t.checkParallel()
+}
+
 func (t *T) checkParallel() {
        // Non-parallel subtests that have parallel ancestors may still
        // run in parallel with other tests: they are only non-parallel