Description
The crypto/rand package contains 1 documented exported global object "Reader", 3 documented exported funcs, and 1 full example showing how to produce 10 random bytes.
It may be non-trivial for some developers to correctly derive common use cases from this API and this example.
It seems that we may have 2 opposite approaches A and B about how we want the crypto/rand package to be used:
A. Crypto is subtle in general. If one is not sure of what they're doing, they shouldn't use the crypto/rand package, risking to use it incorrectly and getting a false feeling of security. This is more or less the warning of the crypto/subtle, applied to all things crypto. So our best advice is to discourage many developers from using crypto/rand.
B. Generating sensitive secrets (nonces, passwords) is a common use case, for which using math/rand is mostly incorrect. math/rand has a useful warning about this "This package's outputs might be easily predictable regardless of how it's seeded. For random numbers suitable for security-sensitive work, see the crypto/rand package." We want to strongly discourage math/rand for sensitive numbers, thus we expect developers to use crypto/rand and encourage them to do so.
The following assumes that our approach is B, not A.
math/rand and crypto/rand have a different API, and sometimes what we want is the convenience and features of the "math" API, with the security provided by the "crypto" package.
As a contrived but realistic example, let's say a non-cryptographer needs to generate a secure random string of 16 characters, where each character is in range 'a'-'z', uniform, per some 3rd party API requirements.
This would be straightforward with the math/rand API:
var alphabet = "abcdefghijklmnopqrstuvwxyz"
func generateNonce() string {
a := make([]byte, 16)
for i := range a {
a[i] = alphabet[rand.Intn(len(alphabet))]
}
return string(a)
}
and more complicated with the crypto/rand API:
func generateNonce() (string, error) {
a := make([]byte, 16)
for i := range a {
bi, err := rand.Int(rand.Reader, big.NewInt(int64(len(alphabet))))
if err != nil {
return "", err
}
k := int(bi.Int64())
a[i] = alphabet[k]
}
return string(a), nil
}
Even worse, the developer could use incorrect shortcuts in good faith, without noticing:
func generateNonce() (string, error) {
a := make([]byte, 16)
_, err := rand.Read(a)
if err != nil {
return "", err
}
for i := range a {
k := a[i] % byte(len(alphabet)) // <- probable bug here
a[i] = alphabet[k]
}
return string(a), nil
}
(this distribution is skewed, which could be bad for security, though I'm not sure how bad exactly)
My suggestion is to embrace B and enrich the documentation of crypto/rand with vetted examples for a few use cases. Before writing any, I'd like to discuss the idea.