golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.14k stars 17.69k forks source link

crypto/rand: add func Text #67057

Open FiloSottile opened 6 months ago

FiloSottile commented 6 months ago

Update, Jun 27 2024: The current proposal is https://github.com/golang/go/issues/67057#issuecomment-2161221119


Random strings are useful as passwords, bearer tokens, and 2FA codes.

Generating them without bias from crypto/rand is not trivial at all, and applications are lured into using math/rand for it. See https://github.com/greenpau/caddy-security/issues/265 for example.

I propose we add a top-level function that takes a charset and returns a string of elements randomly selected from it.

The length of the string is selected automatically to provide 128 bits of security. If uppercase and lowercase letters and digits are used, a string will be ceil(log62(2^128)) = 22 characters long.

If more than 2^48 strings are generated, the chance of collision becomes higher than 2^32, but that's also true of UUID v4. Callers that reach those scales could call String twice and concatenate the results, but it doesn't feel worth documenting.

There is no error return value on the assumption that #66821 is accepted. That allows convenient slicing if necessary.

// String returns a random sequence of characters from the supplied alphabet.
//
// The length of the returned string is selected to provide 128 bits of entropy,
// enough to make a brute-force attack infeasible. If a shorter string is
// needed, for example as a one-time token, the caller may truncate the result.
//
// The alphabet is interpreted as a sequence of runes, and must contain at least
// two Unicode characters, or String will panic.
func String(alphabet string) string

This can't really be implemented in constant time, but since it always runs randomly, attackers can get only a single timing sample, which limits the maximum theoretical leak to a few bits.

Do we already have constants for the most common charsets defined somewhere?

/cc @golang/security @golang/proposal-review

rsc commented 3 months ago

No change in consensus, so accepted. 🎉 This issue now tracks the work of implementing the proposal. — rsc for the proposal review group

The proposal is to add to crypto/rand a single function:

// Text returns a cryptographically random string using the standard RFC 4648 base32 alphabet
// for use when a secret string, token, password, or other text is needed.
// The result contains at least 128 bits of randomness, enough to prevent brute force
// guessing attacks and to make the likelihood of collisions vanishingly small.
// A future version may return longer texts as needed to maintain those properties.
func Text() string

Other features like custom alphabets and custom lengths are deferred to future proposals once we see how well Text works.

magical commented 3 months ago

Apologies for the late response.

  1. It is unclear to me what the intended use cases for this fixed-format Text function are. RSC lamented a lack of uses for custom alphabets, to which several people responded with ideas. Where are the use cases for a fixed alphabet? Personally, I can think of a few hypothetical cases where i may want a string sampled from a particular alphabet, but none where i don't care what the alphabet or length are, and none where i would specifically want it to be base32. The original proposal mentioned "passwords, bearer tokens, and 2FA codes", but as https://github.com/golang/go/issues/67057#issuecomment-2164149580 explained, none of these seem well-served by the latest proposal.

  2. If this must be added, i would prefer it be called rand.Base32. It seems unlikely that the character set can ever be changed once it is added; expanding the character set may break applications which assume it is base32; reducing it would make the strings longer for little benefit. rand.Text is a good, general name and it seems a shame to use it up on such a special case.

  3. Assuming someone wants 128 bits of random base32 text (or any other standard encoding), it seems easy enough to get it:

    var buf = make([]byte, 128/8)
    rand.Read(buf)
    return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(buf)

    The main difference being that, in this snippet, the final character will have only 3 bits of entropy instead of 5, whereas the string returned from Text would sample every character evenly. The proposal fails to motivate why this last-character bias is important enough to address. (And if it is that important, why is it not mentioned in the doc comment?).

ericlagergren commented 3 months ago

The main difference being that, in this snippet, the final character will have only 3 bits of entropy instead of 5, whereas the string returned from Text would sample every character evenly. The proposal fails to motivate why this last-character bias is important enough to address.

This is not the "bias" mentioned in the proposal. The "bias" is people using math/rand or people writing code with modulo bias:

var s string
for len(s) < 11 {
    var buf [8]uint64
    rand.Read(buf[:])
    v := binary.LittleEndian.Uint64(buf[:])
    // INSECURE: modulo bias.
    s += alphabet[v % len(alphabet)]
}

The snippet you posted is indeed secure since the encoding E(x) -> s outputs a cryptographically secure string if x is cryptographically secure and E is injective.

earthboundkid commented 3 months ago

The proposed name change to rand.Base32() seems fine to me.

Of "passwords, bearer tokens, and 2FA codes" this doesn't seem like a fit for passwords (too many arbitrary requirements) or 2FA codes (too hard to type) but it's perfect for a bearer token, and that's a pretty common need.

rsc commented 3 months ago

The proposal was accepted as rand.Text. That's still a good name.

earthboundkid commented 4 days ago

This is accepted and the freeze is starting soon. Is anyone working on getting it in before the freeze?

magical commented 4 days ago

Shh — i was hoping that everyone had forgotten. ;)

rolandshoemaker commented 4 days ago

I don't believe anyone from our team is working on it. If anyone wants to send a CL for it we can probably review it before the freeze.

gopherbot commented 4 days ago

Change https://go.dev/cl/627477 mentions this issue: crypto/rand: add Text for secure random strings