344

I want a random string of characters only (uppercase or lowercase), no numbers, in Go. What is the fastest and simplest way to do this?

Tim Cooper
  • 144,163
  • 35
  • 302
  • 261
Anish Shah
  • 5,940
  • 7
  • 25
  • 39
  • 2
    @VinceEmigh: Here's a meta topic discussing basic questions. http://meta.stackoverflow.com/q/274645/395461 Personally, I think basic questions are ok if written well and are on-topic. Look at the answers below, they illustrate a bunch of things that would be useful for someone new to go. For loops, type casting, make(), etc. – Shannon Matthews Aug 06 '15 at 01:01
  • 3
    @Shannon "*This question does not show any research effort*" (first highly upvoted answer in your link) - That's what I was referring to. He shows no research effort. No effort at all (an attempt, or even stating that he looked online, which he obviously hasn't). Although it would be useful for someone *new*, this site is not focused on teaching new people. It's focused on answering specific programming problems/questions, not tutorials/guides. Although it could be used for the latter, that is not the focus, and thus this question should be closed. Instead, its spoonfed /: – Dioxin Aug 06 '15 at 01:08
  • 11
    @VinceEmigh I asked this question a year back. I had searched online for random strings and read docs too. But It was not helpful. If I haven't written in the question, then it doesn't mean that I haven't researched. – Anish Shah Aug 06 '15 at 17:17

16 Answers16

945

Paul's solution provides a simple, general solution.

The question asks for the "the fastest and simplest way". Let's address the fastest part too. We'll arrive at our final, fastest code in an iterative manner. Benchmarking each iteration can be found at the end of the answer.

All the solutions and the benchmarking code can be found on the Go Playground. The code on the Playground is a test file, not an executable. You have to save it into a file named XX_test.go and run it with

go test -bench . -benchmem

Foreword:

The fastest solution is not a go-to solution if you just need a random string. For that, Paul's solution is perfect. This is if performance does matter. Although the first 2 steps (Bytes and Remainder) might be an acceptable compromise: they do improve performance by like 50% (see exact numbers in the II. Benchmark section), and they don't increase complexity significantly.

Having said that, even if you don't need the fastest solution, reading through this answer might be adventurous and educational.

I. Improvements

1. Genesis (Runes)

As a reminder, the original, general solution we're improving is this:

func init() {
    rand.Seed(time.Now().UnixNano())
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func RandStringRunes(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letterRunes[rand.Intn(len(letterRunes))]
    }
    return string(b)
}

2. Bytes

If the characters to choose from and assemble the random string contains only the uppercase and lowercase letters of the English alphabet, we can work with bytes only because the English alphabet letters map to bytes 1-to-1 in the UTF-8 encoding (which is how Go stores strings).

So instead of:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

we can use:

var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

Or even better:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Now this is already a big improvement: we could achieve it to be a const (there are string constants but there are no slice constants). As an extra gain, the expression len(letters) will also be a const! (The expression len(s) is constant if s is a string constant.)

And at what cost? Nothing at all. strings can be indexed which indexes its bytes, perfect, exactly what we want.

Our next destination looks like this:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func RandStringBytes(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Intn(len(letterBytes))]
    }
    return string(b)
}

3. Remainder

Previous solutions get a random number to designate a random letter by calling rand.Intn() which delegates to Rand.Intn() which delegates to Rand.Int31n().

This is much slower compared to rand.Int63() which produces a random number with 63 random bits.

So we could simply call rand.Int63() and use the remainder after dividing by len(letterBytes):

func RandStringBytesRmndr(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
    }
    return string(b)
}

This works and is significantly faster, the disadvantage is that the probability of all the letters will not be exactly the same (assuming rand.Int63() produces all 63-bit numbers with equal probability). Although the distortion is extremely small as the number of letters 52 is much-much smaller than 1<<63 - 1, so in practice this is perfectly fine.

To make this understand easier: let's say you want a random number in the range of 0..5. Using 3 random bits, this would produce the numbers 0..1 with double probability than from the range 2..5. Using 5 random bits, numbers in range 0..1 would occur with 6/32 probability and numbers in range 2..5 with 5/32 probability which is now closer to the desired. Increasing the number of bits makes this less significant, when reaching 63 bits, it is negligible.

4. Masking

Building on the previous solution, we can maintain the equal distribution of letters by using only as many of the lowest bits of the random number as many is required to represent the number of letters. So for example if we have 52 letters, it requires 6 bits to represent it: 52 = 110100b. So we will only use the lowest 6 bits of the number returned by rand.Int63(). And to maintain equal distribution of letters, we only "accept" the number if it falls in the range 0..len(letterBytes)-1. If the lowest bits are greater, we discard it and query a new random number.

Note that the chance of the lowest bits to be greater than or equal to len(letterBytes) is less than 0.5 in general (0.25 on average), which means that even if this would be the case, repeating this "rare" case decreases the chance of not finding a good number. After n repetition, the chance that we still don't have a good index is much less than pow(0.5, n), and this is just an upper estimation. In case of 52 letters the chance that the 6 lowest bits are not good is only (64-52)/64 = 0.19; which means for example that chances to not have a good number after 10 repetition is 1e-8.

So here is the solution:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func RandStringBytesMask(n int) string {
    b := make([]byte, n)
    for i := 0; i < n; {
        if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i++
        }
    }
    return string(b)
}

5. Masking Improved

The previous solution only uses the lowest 6 bits of the 63 random bits returned by rand.Int63(). This is a waste as getting the random bits is the slowest part of our algorithm.

If we have 52 letters, that means 6 bits code a letter index. So 63 random bits can designate 63/6 = 10 different letter indices. Let's use all those 10:

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
    letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

func RandStringBytesMaskImpr(n int) string {
    b := make([]byte, n)
    // A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
    for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = rand.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

6. Source

The Masking Improved is pretty good, not much we can improve on it. We could, but not worth the complexity.

Now let's find something else to improve. The source of random numbers.

There is a crypto/rand package which provides a Read(b []byte) function, so we could use that to get as many bytes with a single call as many we need. This wouldn't help in terms of performance as crypto/rand implements a cryptographically secure pseudorandom number generator so it's much slower.

So let's stick to the math/rand package. The rand.Rand uses a rand.Source as the source of random bits. rand.Source is an interface which specifies a Int63() int64 method: exactly and the only thing we needed and used in our latest solution.

So we don't really need a rand.Rand (either explicit or the global, shared one of the rand package), a rand.Source is perfectly enough for us:

var src = rand.NewSource(time.Now().UnixNano())

func RandStringBytesMaskImprSrc(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

Also note that this last solution doesn't require you to initialize (seed) the global Rand of the math/rand package as that is not used (and our rand.Source is properly initialized / seeded).

One more thing to note here: package doc of math/rand states:

The default Source is safe for concurrent use by multiple goroutines.

So the default source is slower than a Source that may be obtained by rand.NewSource(), because the default source has to provide safety under concurrent access / use, while rand.NewSource() does not offer this (and thus the Source returned by it is more likely to be faster).

7. Utilizing strings.Builder

All previous solutions return a string whose content is first built in a slice ([]rune in Genesis, and []byte in subsequent solutions), and then converted to string. This final conversion has to make a copy of the slice's content, because string values are immutable, and if the conversion would not make a copy, it could not be guaranteed that the string's content is not modified via its original slice. For details, see How to convert utf8 string to []byte? and golang: []byte(string) vs []byte(*string).

Go 1.10 introduced strings.Builder. strings.Builder is a new type we can use to build contents of a string similar to bytes.Buffer. Internally it uses a []byte to build the content, and when we're done, we can obtain the final string value using its Builder.String() method. But what's cool in it is that it does this without performing the copy we just talked about above. It dares to do so because the byte slice used to build the string's content is not exposed, so it is guaranteed that no one can modify it unintentionally or maliciously to alter the produced "immutable" string.

So our next idea is to not build the random string in a slice, but with the help of a strings.Builder, so once we're done, we can obtain and return the result without having to make a copy of it. This may help in terms of speed, and it will definitely help in terms of memory usage and allocations.

func RandStringBytesMaskImprSrcSB(n int) string {
    sb := strings.Builder{}
    sb.Grow(n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            sb.WriteByte(letterBytes[idx])
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return sb.String()
}

Do note that after creating a new strings.Buidler, we called its Builder.Grow() method, making sure it allocates a big-enough internal slice (to avoid reallocations as we add the random letters).

8. "Mimicing" strings.Builder with package unsafe

strings.Builder builds the string in an internal []byte, the same as we did ourselves. So basically doing it via a strings.Builder has some overhead, the only thing we switched to strings.Builder for is to avoid the final copying of the slice.

strings.Builder avoids the final copy by using package unsafe:

// String returns the accumulated string.
func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

The thing is, we can also do this ourselves, too. So the idea here is to switch back to building the random string in a []byte, but when we're done, don't convert it to string to return, but do an unsafe conversion: obtain a string which points to our byte slice as the string data.

This is how it can be done:

func RandStringBytesMaskImprSrcUnsafe(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return *(*string)(unsafe.Pointer(&b))
}

(9. Using rand.Read())

Go 1.7 added a rand.Read() function and a Rand.Read() method. We should be tempted to use these to read as many bytes as we need in one step, in order to achieve better performance.

There is one small "problem" with this: how many bytes do we need? We could say: as many as the number of output letters. We would think this is an upper estimation, as a letter index uses less than 8 bits (1 byte). But at this point we are already doing worse (as getting the random bits is the "hard part"), and we're getting more than needed.

Also note that to maintain equal distribution of all letter indices, there might be some "garbage" random data that we won't be able to use, so we would end up skipping some data, and thus end up short when we go through all the byte slice. We would need to further get more random bytes, "recursively". And now we're even losing the "single call to rand package" advantage...

We could "somewhat" optimize the usage of the random data we acquire from math.Rand(). We may estimate how many bytes (bits) we'll need. 1 letter requires letterIdxBits bits, and we need n letters, so we need n * letterIdxBits / 8.0 bytes rounding up. We can calculate the probability of a random index not being usable (see above), so we could request more that will "more likely" be enough (if it turns out it's not, we repeat the process). We can process the byte slice as a "bit stream" for example, for which we have a nice 3rd party lib: github.com/icza/bitio (disclosure: I'm the author).

But Benchmark code still shows we're not winning. Why is it so?

The answer to the last question is because rand.Read() uses a loop and keeps calling Source.Int63() until it fills the passed slice. Exactly what the RandStringBytesMaskImprSrc() solution does, without the intermediate buffer, and without the added complexity. That's why RandStringBytesMaskImprSrc() remains on the throne. Yes, RandStringBytesMaskImprSrc() uses an unsynchronized rand.Source unlike rand.Read(). But the reasoning still applies; and which is proven if we use Rand.Read() instead of rand.Read() (the former is also unsynchronzed).

II. Benchmark

All right, it's time for benchmarking the different solutions.

Moment of truth:

BenchmarkRunes-4                     2000000    723 ns/op   96 B/op   2 allocs/op
BenchmarkBytes-4                     3000000    550 ns/op   32 B/op   2 allocs/op
BenchmarkBytesRmndr-4                3000000    438 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMask-4                 3000000    534 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImpr-4            10000000    176 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrc-4         10000000    139 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrcSB-4       10000000    134 ns/op   16 B/op   1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4   10000000    115 ns/op   16 B/op   1 allocs/op

Just by switching from runes to bytes, we immediately have 24% performance gain, and memory requirement drops to one third.

Getting rid of rand.Intn() and using rand.Int63() instead gives another 20% boost.

Masking (and repeating in case of big indices) slows down a little (due to repetition calls): -22%...

But when we make use of all (or most) of the 63 random bits (10 indices from one rand.Int63() call): that speeds up big time: 3 times.

If we settle with a (non-default, new) rand.Source instead of rand.Rand, we again gain 21%.

If we utilize strings.Builder, we gain a tiny 3.5% in speed, but we also achieved 50% reduction in memory usage and allocations! That's nice!

Finally if we dare to use package unsafe instead of strings.Builder, we again gain a nice 14%.

Comparing the final to the initial solution: RandStringBytesMaskImprSrcUnsafe() is 6.3 times faster than RandStringRunes(), uses one sixth memory and half as few allocations. Mission accomplished.

moorara
  • 3,099
  • 7
  • 42
  • 55
icza
  • 289,344
  • 42
  • 658
  • 630
  • 8
    @RobbieV Yup, because a shared `rand.Source` is used. A better workaround would be to pass a `rand.Source` to the `RandStringBytesMaskImprSrc()` function, and that way no locking is required and therefore performance/efficiency is not effected. Each goroutine could have its own `Source`. – icza Aug 14 '15 at 13:05
  • The GoPlayground example doesn't execute, is that intentional? – Jon L. Aug 18 '15 at 12:15
  • @JonL. It's not intentional, but the code on the Playground is a test file, not an executable. You have to save it into a file named `XX_test.go` and run it with `go test -bench .`. – icza Aug 18 '15 at 12:48
  • 148
    @icza, that is one of the best answers I saw for a long time on SO! – astropanic Aug 30 '15 at 12:12
  • @RobbieV here's a slight change to your code - I like the idiom of using `defer` to unlock the mutex right after locking it in simple function scenarios like the one you provided. https://play.golang.org/p/H1P82Jkyhv – Mike Atlas Dec 09 '15 at 21:01
  • 1
    @MikeAtlas: Should avoid using `defer` when it is obvious that you don't need it. See http://grokbase.com/t/gg/golang-nuts/158zz5p42w/go-nuts-defer-performance – Zan Lynx Feb 14 '16 at 23:40
  • 1
    @ZanLynx thx for the tip; although `defer` to unlock a mutex either immediately before or after calling a lock is IMO _mostly_ a very good idea; you're guaranteed to both not forget to unlock but also unlock even in a non-fatal panic mid-function. – Mike Atlas Feb 15 '16 at 17:48
  • 1
    @RobbieV it looks like this code is thread/goroutine safe because the underlying shared source is already a LockedSource which implements the mutex (https://golang.org/src/math/rand/rand.go:259). – adityajones Jun 20 '16 at 22:50
  • If I know the length of the `letterBytes`. I can set the `letterIdxMask` as a individual octal. In the sample, the idx rang is `0-51`, I can set letterIdxMask as `063`. Then `idx := int(cache & letterIdxMask)`, idx is in the range of `0-51`. – firelyu Feb 24 '17 at 09:19
  • to further improve your mask improved move your len out of the loop ```length := len(letterBytes) for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { if idx := int(cache & letterIdxMask); idx < length { } ``` – allyraza Aug 23 '17 at 07:33
  • @allyraza That wouldn't improve anything just make it less readable, as `len(letterBytes)` is a constant value. Read the **"2. Bytes"** section for an explanation. It could even make performance slightly worse as you introduce a variable which needs reading and writing. – icza Aug 23 '17 at 07:35
  • Hi all and @icza, I've taken the liberty to distill all the discussion and wisdom here into https://github.com/ernsheong/grand, please raise an issue there so that we can all benefit and collectively have the fastest and simplest random string generator :) – Jonathan Lin Sep 02 '17 at 07:43
  • @icza, say if I include `0 to 9` `.` and `-` to letterBytes. Can I shrink the code down to `b[i] = letterBytes[int(cache & letterIdxMask)]` (without the if part)? – TLJ Sep 08 '17 at 07:45
  • 1
    @TLJ Yes you can, because you'll have exactly 64 symbols, whose index will be coded using 6 bits, and all bit combinations will be a valid index. Generally, if the number of combinations is a power of 2, the code can be simplified (and thus improved). – icza Sep 08 '17 at 07:53
  • @icza This simple remainder-based implementation benchmark [https://stackimpact.com/golang-benchmarks/random-strings/](https://stackimpact.com/golang-benchmarks/random-strings/) actually outperformed all of these. All tested on the same machine with Go 1.10. – logix Feb 23 '18 at 10:12
  • @logix That implementation does not give you a uniformly distributed random string. To demonstrate, imagine you use 255 random letters, so `size = 255`. Inside `randomString()`, there is this line: `bytes[i] = letters[b%size]`. `b` is a random `byte`, having a random value in the range `0..255`, each value with equal probability. Now `b%size` will give the result `0` with **twice** probability than all other letters. – icza Feb 23 '18 at 10:21
  • @icza That's obviously true. The point was however that the performance improvement comes from using `rand.Read`, according to my tests, which might benefit the masking-based implementation as well. We'll add more implementations in the future. – logix Feb 23 '18 at 10:45
  • @logix The thing is that if I take my fastest solution `RandStringBytesMaskImprSrc()`, and I remove the condition which makes sure it uses all letters with equal probability, this condition: `if idx := int(cache & letterIdxMask); idx < len(letterBytes)`, and I just use the remaineder `%` like in your example, then this solution outperforms performance-wise your proposed `rand.Read()`. And the reason for this is because `rand.Read()` uses a loop and keeps calling `Source.Int63()` until it fills the passed slice. My solution does the same without the intermediate `[]byte` buffer. – icza Feb 23 '18 at 11:25
  • @logix And the "modified" `RandStringBytesMaskImprSrc()` outperforms your proposed solution even if you'd use the `Rand.Read()` method instead of `rand.Read()` (the former is not synchronized, so it's a little faster than `rand.Read()`). – icza Feb 23 '18 at 11:27
  • Why after refreshing, like making new request, the result is the same string? What's purpose of using init function and logic code inside it? – TomSawyer Mar 20 '18 at 09:21
  • @TomSawyer You have to run it locally, on your computer. On the Go Playground, response is cached and time always starts at the same time (and thus time-related seeding may always be the same). For details, see [this](https://stackoverflow.com/a/32966784/1705598) and [this](https://stackoverflow.com/a/36706499/1705598). – icza Mar 20 '18 at 12:27
  • How unique is the string that generated by this method? – dev-jim Oct 18 '18 at 17:33
  • 1
    @dev-jim The "quality" of the above solutions depends on the "randomness" of `math.Random`. If we assume it's perfectly random, then the above solutions generate any possible random strings (with the given length) with equal probability. This is the perfect / optimal outcome. – icza Oct 18 '18 at 19:46
  • 1
    Simple, Correct, Fast: in that order. This answer is fast and may be correct, but it is not simple. I recommend budding developers out there not use it unless they've already identified this function as a bottleneck in their code. – Steven Soroka Jan 30 '19 at 19:58
  • 2
    @StevenSoroka I agree with you. The fastest solution is not a go-to solution if you just need a random string. For that, Paul's solution is perfect. This is if performance does matter. Although the first 2 steps ("Bytes" and "Remainder") might be an acceptable compromise: they do improve performance by like 50%, and they don't increase complexity significantly. – icza Jan 31 '19 at 09:25
  • 1
    I frequently reference this answer. Thanks. One question: Is there any way to (efficiently) recycle (rather than discard) the random bits in the case that the index is > len(letterBytes)? I've played with this myself a bit, and haven't found a solution that improves performance, but maybe you'll think of one? – Flimzy Mar 22 '19 at 10:25
  • 1
    @Flimzy Good idea. I made some quick changes to utilize (some) of the discarded bits, and it came out slower (due to increased complexity). The one that was faster distorted the equal distribution slightly... But I will work on it more when I have time to see if a significant improvement can be reached simply. – icza Mar 22 '19 at 15:06
  • 2
    @Flimzy I played a little more with it, and still wasn't able to come up with a solution that turns the discarded bits into a speed advantage (while maintaining equal distribution). Although revisiting this question, I did added 2 additional solutions to further improve performance (utilizing `strings.Builder` and an "unsafe" solution). – icza Mar 22 '19 at 23:12
158

You can just write code for it. This code can be a little simpler if you want to rely on the letters all being single bytes when encoded in UTF-8.

package main

import (
    "fmt"
    "time"
    "math/rand"
)

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randSeq(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letters[rand.Intn(len(letters))]
    }
    return string(b)
}

func main() {
    rand.Seed(time.Now().UnixNano())

    fmt.Println(randSeq(10))
}
inanc
  • 16,581
  • 8
  • 70
  • 83
Paul Hankin
  • 44,768
  • 11
  • 79
  • 97
  • 36
    Don't forget about the rand.Seed(), otherwise you got the same string every first time launch... rand.Seed(time.Now().UTC().UnixNano()) – Evan Lin Jan 22 '15 at 15:59
  • 3
    Evan's addition is correct, however there are other similar options: `rand.Seed(time.Now().Unix())` or `rand.Seed(time.Now().UnixNano())` – openwonk Jun 20 '15 at 21:50
  • 9
    For a hard-to-guess secret--a password, a crypto key, etc.--never use `math/rand`; use `crypto/rand` (like @Not_A_Golfer's option 1) instead. – twotwotwo Aug 06 '15 at 05:53
  • 1
    @EvanLin Won't this be guessable? If I have to seed the generator, then the attacker could guess the time i'm seeding it with and predict the same output that i'm generating. – Matej Oct 22 '15 at 10:33
  • 4
    Note that if you're trying the above program with seed, on go playground, it will return same result all the time. I was trying it out on playground and after a some time realized this. It worked fine otherwise for me. Hope it saves someones time :) – SeattleOrBayArea Apr 01 '16 at 22:51
  • @GauravSinha The Go Playground has `time.Now()` set to always return `2009-11-10 23:00:00 +0000 UTC` no matter what. – HellaMad Dec 30 '16 at 04:18
  • @EvanLin Where to put `rand.Seed`? – TomSawyer Mar 20 '18 at 09:22
27

Use package uniuri, which generates cryptographically secure uniform (unbiased) strings.

Disclaimer: I'm the author of the package

Jean-François Fabre
  • 126,787
  • 22
  • 103
  • 165
dchest
  • 1,438
  • 18
  • 20
  • 1
    Thanks for this. While I appreciate the ample detail in other answers, your answer provided a quick practical way to generate the short random string I wanted. – Jason McVetta Nov 11 '20 at 08:10
18

Two possible options (there might be more of course):

  1. You can use the crypto/rand package that supports reading random byte arrays (from /dev/urandom) and is geared towards cryptographic random generation. see http://golang.org/pkg/crypto/rand/#example_Read . It might be slower than normal pseudo-random number generation though.

  2. Take a random number and hash it using md5 or something like this.

Not_a_Golfer
  • 40,006
  • 7
  • 115
  • 81
5

Following icza's wonderfully explained solution, here is a modification of it that uses crypto/rand instead of math/rand.

const (
    letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 52 possibilities
    letterIdxBits = 6                    // 6 bits to represent 64 possibilities / indexes
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func SecureRandomAlphaString(length int) string {

    result := make([]byte, length)
    bufferSize := int(float64(length)*1.3)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            randomBytes = SecureRandomBytes(bufferSize)
        }
        if idx := int(randomBytes[j%length] & letterIdxMask); idx < len(letterBytes) {
            result[i] = letterBytes[idx]
            i++
        }
    }

    return string(result)
}

// SecureRandomBytes returns the requested number of bytes using crypto/rand
func SecureRandomBytes(length int) []byte {
    var randomBytes = make([]byte, length)
    _, err := rand.Read(randomBytes)
    if err != nil {
        log.Fatal("Unable to generate random bytes")
    }
    return randomBytes
}

If you want a more generic solution, that allows you to pass in the slice of character bytes to create the string out of, you can try using this:

// SecureRandomString returns a string of the requested length,
// made from the byte characters provided (only ASCII allowed).
// Uses crypto/rand for security. Will panic if len(availableCharBytes) > 256.
func SecureRandomString(availableCharBytes string, length int) string {

    // Compute bitMask
    availableCharLength := len(availableCharBytes)
    if availableCharLength == 0 || availableCharLength > 256 {
        panic("availableCharBytes length must be greater than 0 and less than or equal to 256")
    }
    var bitLength byte
    var bitMask byte
    for bits := availableCharLength - 1; bits != 0; {
        bits = bits >> 1
        bitLength++
    }
    bitMask = 1<<bitLength - 1

    // Compute bufferSize
    bufferSize := length + length / 3

    // Create random string
    result := make([]byte, length)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            // Random byte buffer is empty, get a new one
            randomBytes = SecureRandomBytes(bufferSize)
        }
        // Mask bytes to get an index into the character slice
        if idx := int(randomBytes[j%length] & bitMask); idx < availableCharLength {
            result[i] = availableCharBytes[idx]
            i++
        }
    }

    return string(result)
}

If you want to pass in your own source of randomness, it would be trivial to modify the above to accept an io.Reader instead of using crypto/rand.

Chris
  • 561
  • 5
  • 7
4

If you want cryptographically secure random numbers, and the exact charset is flexible (say, base64 is fine), you can calculate exactly what the length of random characters you need from the desired output size.

Base 64 text is 1/3 longer than base 256. (2^8 vs 2^6; 8bits/6bits = 1.333 ratio)

import (
    "crypto/rand"
    "encoding/base64"
    "math"
)

func randomBase64String(l int) string {
    buff := make([]byte, int(math.Ceil(float64(l)/float64(1.33333333333))))
    rand.Read(buff)
    str := base64.RawURLEncoding.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

Note: you can also use RawStdEncoding if you prefer + and / characters to - and _

If you want hex, base 16 is 2x longer than base 256. (2^8 vs 2^4; 8bits/4bits = 2x ratio)

import (
    "crypto/rand"
    "encoding/hex"
    "math"
)


func randomBase16String(l int) string {
    buff := make([]byte, int(math.Ceil(float64(l)/2)))
    rand.Read(buff)
    str := hex.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

However, you could extend this to any arbitrary character set if you have a base256 to baseN encoder for your character set. You can do the same size calculation with how many bits are needed to represent your character set. The ratio calculation for any arbitrary charset is: ratio = 8 / log2(len(charset))).

Though both of these solutions are secure, simple, should be fast, and don't waste your crypto entropy pool.

Here's the playground showing it works for any size. https://play.golang.org/p/_yF_xxXer0Z

Steven Soroka
  • 18,179
  • 4
  • 47
  • 38
  • worth mentioning that Go Playground always returns the same random number, so you won't see in there different random strings at different executions of that code – TPPZ Jul 30 '19 at 09:39
  • 1
    The randomBase64String() func is really good answer, because it combines the high-density base64 encoding with crypto/rand data generation. BUT, it has a bug. When you ask for 3 characters, it will generate two random bytes, only giving you 16 bits of entropy, not 18 bits of entropy. I suggest you use buff:=make([]byte, ((6*l)+7)/8), if you want all the bits to be random (without generating excess random entropy, either). This playground snippet shows the original example fails to generate sufficient bits to fill out the base64 frequently. https://play.golang.org/p/uVLosTt1jyo – Cameron Oct 15 '20 at 00:12
  • 1
    Thanks @Cameron, I think math.Ceil will avoid this problem. I'll update the post. – Steven Soroka Apr 11 '21 at 02:34
3

Here is my way ) Use math rand or crypto rand as you wish.

func randStr(len int) string {
    buff := make([]byte, len)
    rand.Read(buff)
    str := base64.StdEncoding.EncodeToString(buff)
    // Base 64 can be longer than len
    return str[:len]
}
Dima
  • 167
  • 1
  • 4
3
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}
kevin
  • 151
  • 4
2

Another version, inspired from generate password in JavaScript crypto:

package main

import (
    "crypto/rand"
    "fmt"
)

var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"

func shortID(length int) string {
    ll := len(chars)
    b := make([]byte, length)
    rand.Read(b) // generates len(b) random bytes
    for i := 0; i < length; i++ {
        b[i] = chars[int(b[i])%ll]
    }
    return string(b)
}

func main() {
    fmt.Println(shortID(18))
    fmt.Println(shortID(18))
    fmt.Println(shortID(18))
}
Ninh Pham
  • 4,298
  • 35
  • 26
1

Here is a simple and performant solution for a cryptographically secure random string.

package main

import (
    "crypto/rand"
    "unsafe"
    "fmt"
)

var alphabet = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func main() {
    fmt.Println(generate(16))
}

func generate(size int) string {
    b := make([]byte, size)
    rand.Read(b)
    for i := 0; i < size; i++ {
        b[i] = alphabet[b[i] / 5]
    }
    return *(*string)(unsafe.Pointer(&b))
}

Benchmark

Benchmark  95.2 ns/op      16 B/op      1 allocs/op
twharmon
  • 2,840
  • 1
  • 17
  • 40
0

If you are willing to add a few characters to your pool of allowed characters, you can make the code work with anything which provides random bytes through a io.Reader. Here we are using crypto/rand.

// len(encodeURL) == 64. This allows (x <= 265) x % 64 to have an even
// distribution.
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

// A helper function create and fill a slice of length n with characters from
// a-zA-Z0-9_-. It panics if there are any problems getting random bytes.
func RandAsciiBytes(n int) []byte {
    output := make([]byte, n)

    // We will take n bytes, one byte for each character of output.
    randomness := make([]byte, n)

    // read all random
    _, err := rand.Read(randomness)
    if err != nil {
        panic(err)
    }

    // fill output
    for pos := range output {
        // get random item
        random := uint8(randomness[pos])

        // random % 64
        randomPos := random % uint8(len(encodeURL))

        // put into output
        output[pos] = encodeURL[randomPos]
    }

    return output
}
0xcaff
  • 10,837
  • 3
  • 39
  • 54
0

You can use this func:

func randomString(length int) string {
    b := make([]byte, length)
    rand.Read(b)
    return fmt.Sprintf("%x", b)[:length]
}

Check it out in the playground

Amin Shojaei
  • 1,943
  • 1
  • 14
  • 26
0

This is a sample code which I used to generate certificate number in my app.

func GenerateCertificateNumber() string {
    CertificateLength := 7
    t := time.Now().String()
    CertificateHash, err := bcrypt.GenerateFromPassword([]byte(t), bcrypt.DefaultCost)
    if err != nil {
        fmt.Println(err)
    }
    // Make a Regex we only want letters and numbers
    reg, err := regexp.Compile("[^a-zA-Z0-9]+")
    if err != nil {
        log.Fatal(err)
    }
    processedString := reg.ReplaceAllString(string(CertificateHash), "")
    fmt.Println(string(processedString))

    CertificateNumber := strings.ToUpper(string(processedString[len(processedString)-CertificateLength:]))
    fmt.Println(CertificateNumber)
    return CertificateNumber
}
Krishnadas PC
  • 3,966
  • 34
  • 33
-1
/*
    korzhao
*/

package rand

import (
    crand "crypto/rand"
    "math/rand"
    "sync"
    "time"
    "unsafe"
)

// Doesn't share the rand library globally, reducing lock contention
type Rand struct {
    Seed int64
    Pool *sync.Pool
}

var (
    MRand    = NewRand()
    randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)

// init random number generator
func NewRand() *Rand {
    p := &sync.Pool{New: func() interface{} {
        return rand.New(rand.NewSource(getSeed()))
    },
    }
    mrand := &Rand{
        Pool: p,
    }
    return mrand
}

// get the seed
func getSeed() int64 {
    return time.Now().UnixNano()
}

func (s *Rand) getrand() *rand.Rand {
    return s.Pool.Get().(*rand.Rand)
}
func (s *Rand) putrand(r *rand.Rand) {
    s.Pool.Put(r)
}

// get a random number
func (s *Rand) Intn(n int) int {
    r := s.getrand()
    defer s.putrand(r)

    return r.Intn(n)
}

//  bulk get random numbers
func (s *Rand) Read(p []byte) (int, error) {
    r := s.getrand()
    defer s.putrand(r)

    return r.Read(p)
}

func CreateRandomString(len int) string {
    b := make([]byte, len)
    _, err := MRand.Read(b)
    if err != nil {
        return ""
    }
    for i := 0; i < len; i++ {
        b[i] = randlist[b[i]%(62)]
    }
    return *(*string)(unsafe.Pointer(&b))
}

24.0 ns/op 16 B/op 1 allocs/

blackgreen
  • 4,019
  • 8
  • 23
  • 41
  • Hello! Welcome to StackOverflow. Although you added a code snippet, your answer doesn't include any context about "how it works" or "why this is the way its done". Also please remember the question is asked in English so your comments should also be in English. – Cengiz Can Jun 19 '20 at 11:33
-1

how random count in :

count, one := big.NewInt(0), big.NewInt(1)
count.SetString("100000000000000000000000", 10)
-3
const (
    chars       = "0123456789_abcdefghijkl-mnopqrstuvwxyz" //ABCDEFGHIJKLMNOPQRSTUVWXYZ
    charsLen    = len(chars)
    mask        = 1<<6 - 1
)

var rng = rand.NewSource(time.Now().UnixNano())

// RandStr 返回指定长度的随机字符串
func RandStr(ln int) string {
    /* chars 38个字符
     * rng.Int63() 每次产出64bit的随机数,每次我们使用6bit(2^6=64) 可以使用10次
     */
    buf := make([]byte, ln)
    for idx, cache, remain := ln-1, rng.Int63(), 10; idx >= 0; {
        if remain == 0 {
            cache, remain = rng.Int63(), 10
        }
        buf[idx] = chars[int(cache&mask)%charsLen]
        cache >>= 6
        remain--
        idx--
    }
    return *(*string)(unsafe.Pointer(&buf))
}

BenchmarkRandStr16-8 20000000 68.1 ns/op 16 B/op 1 allocs/op

zxysilent
  • 3
  • 1