Skip to content

crypto/rand: add func Text #67057

Description

@FiloSottile

Update, Jun 27 2024: The current proposal is #67057 (comment)


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

Activity

  1. added this to the Proposal milestone on Apr 26, 2024
  2. robpike commented on Apr 26, 2024

    @robpike
    Contributor

    Is there a reason to set the entropy to a hidden value rather than provide it as a parameter (other than Fillipo knows best)? What if I need more randomness tomorrow?

    The idea is cool but it seems too rigid to me, but I am not a security maven.

  3. FiloSottile commented on Apr 26, 2024

    @FiloSottile
    ContributorAuthor

    I started with taking an int parameter for the entropy, then started going back and forth on whether it should be the length of the returned string (what if the caller does the math wrong, or does it right but later changes the alphabet to be smaller thinking the parameter is the entropy and doesn't need to change?) or the bits of entropy (what if the caller thinks it's the length of the string and passes a way too small number?).

    This is one of those cases where we can do the work for the user and do the secure thing directly. So yeah, we can know what the best answer is, so we can save callers the work and risk.

    If a caller knows they are fine with less entropy (e.g. for a PAKE or 2FA token), they can slice the string. The performance overhead is unlikely to be a bottleneck, and the slicing can alert a security reviewer to pay attention.

    There's no need for more entropy than 128 bits against brute force attacks. If making hundreds of thousands of billions of strings users might need more entropy against collisions, but they will probably have someone on the team that knows that by the time they design such a system, and they can just call String() + String(). UUIDv4 made the same assumption and seems fine.

  4. moved this to Incoming in Proposalson Apr 26, 2024
  5. ulikunitz commented on Apr 27, 2024

    @ulikunitz
    Contributor

    I find the String() function with a parameter strange because the String() method doesn't usually have parameters.

    The approach I usually apply is to use crypto/rand.Reader to get 16 random bytes and converting it with base64.RawURLEncoding.EncodeToString(). The resulting string reflects all the bits in the random value. Using this approach a RandomStringer with a String() method is quite easy to implement. crypto/rand could have a String() function that uses an internal default RandomStringer value. IMHO this approach would be more efficient than a String function that has to validate the alphabet parameter at every call.

  6. jfrech commented on Apr 28, 2024

    @jfrech

    Would String("aab") exhibit a bias?

  7. FiloSottile commented on Apr 30, 2024

    @FiloSottile
    ContributorAuthor

    @ulikunitz Efficiency is not a primary concern here, I don't think applications generate passwords or tokens in a hot loop, at least not hot enough that checking that a string is valid UTF-8 will matter.

    Would String("aab") exhibit a bias?

    Yes, a would be selected twice as often as b. We should document that.

  8. ulikunitz commented on Apr 30, 2024

    @ulikunitz
    Contributor

    Is String("aaa") a valid use? What about String("")? Both strings are valid UTF-8. Josua Bloch stated an API should be hard to misuse.

    This API can be misused to generate always the same string or biased strings.

    The following API doesn't have these weaknesses:

    func NewAlphabet(s string) (ab *Alphabet, err error)
    
    func (ab *Alphabet) RandomString() string
    
    const Base32DouglasCrockford = "0123456789ABCDEFGHJKMNPQRSTVWXZ"
    
    // String generates a random string using Douglas Crockford's
    // [base32] alphabet.
    // [base32]: https://www.crockford.com/base32.html
    func String() string
    

    The alphabet will produce slightly larger strings, but according to Crockford is a good compromise between compactness and error resistance. I named the method RandomString() to be explicit about what the method does. The String() function is ok, because it will be used as rand.String() in almost all cases.

    I assume that the String function will not support Unicode combining characters.

    Some people might want to use the function to create keys for databases. They might be concerned about performance.

  9. ericlagergren commented on May 1, 2024

    @ericlagergren
    Contributor

    I like the proposal, but I'm also concerned about the alphabet just being a plain string. Inevitably somebody will read the alphabet in from a config file or basically anything other than const MyAlphabet = "..." (like attacker controlled data!).

  10. jfrech commented on May 2, 2024

    @jfrech

    @ulikunitz

    Is String("aaa") a valid use? What about String("")? Both strings are valid UTF-8.

    Please refer to the proposed function's documentation's last paragraph (where "two" is sensibly read as "two different"):

    // (...)
    // 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

    I urge you not to dilute the expressive power of the standard library by inventing novel concepts (such as *Alphabet) where plain strings suffice.

  11. ulikunitz commented on May 2, 2024

    @ulikunitz
    Contributor

    I apologize for overseeing the panic condition. I suggest however to check for the runes in the string to be unique or as my math teacher used to say pairwise different.

    I did an experimental implementation and testing for uniqueness is not dramatically slower than testing for at least two different runes for short alphabets. And with 2 ms per call an Alphabet type is not required.

    goos: linux
    goarch: amd64
    pkg: github.com/ulikunitz/randstr
    cpu: AMD Ryzen 7 3700X 8-Core Processor             
    BenchmarkString1/base10-16         	  896253	      2265 ns/op
    BenchmarkString1/base26-16         	  782935	      1845 ns/op
    BenchmarkString1/base36-16         	  490089	      2165 ns/op
    BenchmarkString1/base62-16         	  466987	      2162 ns/op
    BenchmarkString2/base10-16         	  591403	      2227 ns/op
    BenchmarkString2/base26-16         	  631990	      1747 ns/op
    BenchmarkString2/base36-16         	  944583	      2174 ns/op
    BenchmarkString2/base62-16         	  488211	      2238 ns/op
    

    String1 tests for at least two different runes. String2 checks for a uniqueness.

    The implementation can be found here: https://github.com/ulikunitz/randstr/blob/main/string.go

    (Updated the comment, had a problem with the computation of the number of runes required.)

  12. ulikunitz commented on May 2, 2024

    @ulikunitz
    Contributor

    After review I observed that I needed more than 128 random bits to ensure that the last character is selected from the full alphabet. The benchmark results are now:

    goos: linux
    goarch: amd64
    pkg: github.com/ulikunitz/randstr
    cpu: AMD Ryzen 7 3700X 8-Core Processor             
    BenchmarkString1/base10-16                668701              2355 ns/op
    BenchmarkString1/base26-16                554954              2759 ns/op
    BenchmarkString1/base36-16                526098              2544 ns/op
    BenchmarkString1/base62-16                634251              2497 ns/op
    BenchmarkString2/base10-16                369050              2782 ns/op
    BenchmarkString2/base26-16                552849              2251 ns/op
    BenchmarkString2/base36-16                426348              2476 ns/op
    BenchmarkString2/base62-16                423656              2515 ns/op
    
  13. 54 remaining items

  14. earthboundkid commented on Aug 1, 2024

    @earthboundkid
    Contributor

    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.

  15. rsc commented on Aug 7, 2024

    @rsc
    Contributor

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

  16. earthboundkid commented on Nov 14, 2024

    @earthboundkid
    Contributor

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

  17. magical commented on Nov 14, 2024

    @magical
    Contributor

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

  18. rolandshoemaker commented on Nov 14, 2024

    @rolandshoemaker
    Member

    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.

  19. gopherbot commented on Nov 14, 2024

    @gopherbot
    Contributor

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

  20. added a commit that references this issue on Nov 21, 2024
    50087aa
  21. modified the milestones: Backlog, Go1.24 on Nov 22, 2024
  22. awnumar commented on Apr 16, 2025

    @awnumar
    Contributor

    We've encountered users who need slightly longer outputs than rand.Text() provides, and so what they've done is copy the source code of Text, extend the alphabet and the number of bytes, and use that instead. Their implementation suffered from a modulo bias.

    The better solution there was to use: rand.Text() + rand.Text(), but should there also be a comment in the code to indicate to readers that there exists the risk of a vulnerable implementation if the size of the alphabet is changed? At a glance the code seems straightforward so I can understand why some may think there are no dragons.

  23. earthboundkid commented on Apr 16, 2025

    @earthboundkid
    Contributor

    I think for that kind of internal doc change, you can just file a CL and it will probably be approved without much discussion.

  24. locked and limited conversation to collaborators on Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions