241

I am trying to generate a random string in Go and here is the code I have written so far:

package main

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

func main() {
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    var result bytes.Buffer
    var temp string
    for i := 0; i < l; {
        if string(randInt(65, 90)) != temp {
            temp = string(randInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

func randInt(min int, max int) int {
    rand.Seed(time.Now().UTC().UnixNano())
    return min + rand.Intn(max-min)
}

My implementation is very slow. Seeding using time brings the same random number for a certain time, so the loop iterates again and again. How can I improve my code?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
copperMan
  • 2,435
  • 2
  • 14
  • 5
  • 2
    The "if string(randInt(65,90))!=temp {" looks like you are trying to add extra security but hey, things get the same one after other by chance. By doing this you may be actually lowering the entropy. – Jan Matějka Oct 23 '13 at 22:37
  • 3
    As a side note, there is no need to convert to UTC in "time.Now().UTC().UnixNano()". Unix time is calculated since Epoch which is UTC anyway. – Grzegorz Luczywo Aug 02 '15 at 14:03
  • 2
    You should set the seed once, only one time, and never more than once. well, in case your application runs for days you could set it once a day. – Casperah Feb 09 '17 at 13:53
  • You should seed once. And I think "Z" may never appear, I guess? So I prefer to use begin index inclusive and end index exclusive. – Jaehyun Yeom Jun 27 '19 at 03:54

12 Answers12

299

Each time you set the same seed, you get the same sequence. So of course if you're setting the seed to the time in a fast loop, you'll probably call it with the same seed many times.

In your case, as you're calling your randInt function until you have a different value, you're waiting for the time (as returned by Nano) to change.

As for all pseudo-random libraries, you have to set the seed only once, for example when initializing your program unless you specifically need to reproduce a given sequence (which is usually only done for debugging and unit testing).

After that you simply call Intn to get the next random integer.

Move the rand.Seed(time.Now().UTC().UnixNano()) line from the randInt function to the start of the main and everything will be faster. And lose the .UTC() call since:

UnixNano returns t as a Unix time, the number of nanoseconds elapsed since January 1, 1970 UTC.

Note also that I think you can simplify your string building:

package main

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

func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    bytes := make([]byte, l)
    for i := 0; i < l; i++ {
        bytes[i] = byte(randInt(65, 90))
    }
    return string(bytes)
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}
wasmup
  • 14,541
  • 6
  • 42
  • 58
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • Thanks for explaining that, i thought this need to be seeded every time. – copperMan Sep 07 '12 at 16:03
  • 27
    You can also add `rand.Seed(...)` to the function `init()`. `init()` is called automatically before `main()`. Note that you don't need to call `init()` from `main()`! – Jabba Feb 03 '14 at 12:30
  • 2
    @Jabba Right. I was keeping my answer as simple as possible and not too far from the question, but your observation is right. – Denys Séguret Feb 03 '14 at 12:35
  • Very precise explanation. – Jingguo Yao Nov 08 '15 at 03:41
  • 8
    Please note that none of the anwers posted so far initialize the seed in a cryptographically secure way. Depending on your application, this might not matter at all or it might result in catastrophic failure. – Ingo Blechschmidt Feb 10 '16 at 00:13
  • Your randInt function as it is written is exclusive of max. It should be: `return min + rand.Intn(max - min + 1) ` – Domenic D. Aug 17 '16 at 13:23
  • @DomenicD. This is the standard practice. You won't find any standard random value function not excluding the range's max. – Denys Séguret Aug 17 '16 at 13:34
  • 3
    @IngoBlechschmidt `math/rand` is not cryptographically secure anyway. If that is a requirement, `crypto/rand` should be used. – Duncan Jones Jul 23 '19 at 12:58
  • rand.Seed has been deprecated since Go 1.20 and an alternative has been available since Go 1.0 – Shah Mar 30 '23 at 14:05
150

Edit: This issue has been addressed as of Go version 1.20 and it is no longer necessary to seed the random source yourself.

I don't understand why people are seeding with a time value. This has in my experience never been a good idea. For example, while the system clock is maybe represented in nanoseconds, the system's clock precision isn't nanoseconds.

This program should not be run on the Go playground but if you run it on your machine you get a rough estimate on what type of precision you can expect. I see increments of about 1000000 ns, so 1 ms increments. That's 20 bits of entropy that are not used. All the while the high bits are mostly constant!? Roughly ~24 bits of entropy over a day which is very brute forceable (which can create vulnerabilities).

The degree that this matters to you will vary but you can avoid pitfalls of clock based seed values by simply using the crypto/rand.Read as source for your seed. It will give you that non-deterministic quality that you are probably looking for in your random numbers (even if the actual implementation itself is limited to a set of distinct and deterministic random sequences).

import (
    crypto_rand "crypto/rand"
    "encoding/binary"
    math_rand "math/rand"
)

func init() {
    var b [8]byte
    _, err := crypto_rand.Read(b[:])
    if err != nil {
        panic("cannot seed math/rand package with cryptographically secure random number generator")
    }
    math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
}

As a side note but in relation to your question. You can create your own rand.Source using this method to avoid the cost of having locks protecting the source. The rand package utility functions are convenient but they also use locks under the hood to prevent the source from being used concurrently. If you don't need that you can avoid it by creating your own Source and use that in a non-concurrent way. Regardless, you should NOT be reseeding your random number generator between iterations, it was never designed to be used that way.


Edit: I used to work in ITAM/SAM and the client we built (then) used a clock based seed. After a Windows update a lot of machines in the company fleet rebooted at roughly the same time. This caused an involtery DoS attack on upstream server infrastructure because the clients was using system up time to seed randomness and these machines ended up more or less randomly picking the same time slot to report in. They were meant to smear the load over a period of an hour or so but that did not happen. Seed responsbily!

John Leidegren
  • 59,920
  • 20
  • 131
  • 152
  • 24
    This answer is very underappreciated. Specially for command line tools that may run multiple times in a second, this is a must do. Thank you – saeedgnu Apr 02 '19 at 04:42
  • 1
    You could mix in the PID and hostname/MAC if needed, but beware that seeding the RNG with a cryptographically-safe source doesn't make it cryptographically secure as someone can reconstruct the PRNG internal state. – Nick T Dec 05 '19 at 19:16
  • 1
    @NickT that doesn't actually do anything. The CSPRNG implementations already do that. You aren't adding anything by doing that yourself. Also, depending on how this is carried out it could potentially skew the distribution of bits. Which I think is bad and a risk you should not take. – John Leidegren Aug 26 '20 at 11:52
  • Why are you mixing math/rand with crypto/rand? The seeding with crypto/random is not necessary. https://golang.org/pkg/crypto/rand/#example_Read – Jan Bodnar Oct 06 '20 at 15:50
  • 1
    @JanBodnar yes, it absolutely is. You can verify this yourself by calling the math/rand functions that use the default source. They will always return the same value unless you change the seed of the default source https://play.golang.org/p/xx83MtoL3WE (click run multiple times). Here we use the crypto/rand package to seed the math/rand default source because unless you really need cryptographically secure randomness it's much better to just use the math/rand package but if we want different numbers each time the program is run we need to seed properly. – John Leidegren Oct 11 '20 at 07:34
  • I made a module out of it: **[RanGo](https://github.com/YektaDev/RanGo)**. – YektaDev Oct 28 '20 at 14:57
  • Would it be a good solution to do seed(time.Now()) and then re-seed with the first 8 random bytes as int64 from the current seed, and then do this 100,000 times. Wouldn't that increase entropy? Or, at least, slow down brute force attacks? – rdnobrega Apr 29 '21 at 04:30
  • @rdnobrega I don't think that it will do much. The PRNG is deterministic and you're just making it restart at a different place but it is still being initialized with a time based seed. Use the best entropy available if you want randomness and don't try to be clever thinking it will improve the situation. – John Leidegren May 26 '21 at 06:41
  • This is cool if you need safety. Lots of programs don't though. – JohnAllen Nov 08 '22 at 10:52
  • 1
    @JohnLeidegren You might want to update your comment. Atleast with regards to the playground. play.golang.org/p/xx83MtoL3WE - does indeed give different values now every time, without any change needed to original source seed. (atleast with go 1.20) – The 0bserver May 08 '23 at 06:00
  • @The0bserver Yes finally. I just noticed this myself. Good on them for fixing this! – John Leidegren Sep 01 '23 at 17:54
18

just to toss it out for posterity: it can sometimes be preferable to generate a random string using an initial character set string. This is useful if the string is supposed to be entered manually by a human; excluding 0, O, 1, and l can help reduce user error.

var alpha = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"

// generates a random string of fixed size
func srand(size int) string {
    buf := make([]byte, size)
    for i := 0; i < size; i++ {
        buf[i] = alpha[rand.Intn(len(alpha))]
    }
    return string(buf)
}

and I typically set the seed inside of an init() block. They're documented here: http://golang.org/doc/effective_go.html#init

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
jorelli
  • 8,064
  • 4
  • 36
  • 35
  • 10
    As far as I understand correctly, there is no need to have `-1` in `rand.Intn(len(alpha)-1)`. This is because `rand.Intn(n)` always returns a number which is less than `n` (in other words: from zero to `n-1` inclusive). – snap Jan 07 '13 at 17:52
  • 2
    @snap is correct; in fact, including the `-1` in `len(alpha)-1` would have guaranteed that the number 9 was never used in the sequence. – carbocation Feb 26 '15 at 18:47
  • 2
    It should also be noted that excluding 0 (zero) is a good idea because you're casting the byte slice to a string, and that causes the 0 to become a null byte. E.g., try creating a file with a '0' byte in the middle and see what happens. – Eric Lagergren Jun 27 '15 at 22:40
15

OK why so complex!

package main

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

func main() {
    rand.Seed( time.Now().UnixNano())
    var bytes int

    for i:= 0 ; i < 10 ; i++{ 
        bytes = rand.Intn(6)+1
        fmt.Println(bytes)
        }
    //fmt.Println(time.Now().UnixNano())
}

This is based off the dystroy's code but fitted for my needs.

It's die six (rands ints 1 =< i =< 6)

func randomInt (min int , max int  ) int {
    var bytes int
    bytes = min + rand.Intn(max)
    return int(bytes)
}

The function above is the exactly same thing.

I hope this information was of use.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Luviz
  • 159
  • 2
  • That will return all the time the very same sequence, in the very same order if called multiple times, that does not look very random to me. Check live example: https://play.golang.org/p/fHHENtaPv5 `3 5 2 5 4 2 5 6 3 1` – Thomas Modeneis Oct 06 '16 at 13:05
  • 10
    @ThomasModeneis: That's because they [fake time](https://blog.golang.org/playground#TOC_3.1.) in the playground. – ofavre Nov 21 '16 at 17:39
  • 1
    Thanks @ofavre, that fake-time really threw me at first. – Jesse Chisholm Jul 22 '17 at 01:20
  • 1
    You still need to seed before calling `rand.Intn()`, otherwise you'll always get the same number any time you run your program. – Flavio Copes Jul 22 '17 at 15:53
  • Any reason for `var bytes int`? What's the difference to changing the above `bytes = rand.Intn(6)+1` to `bytes := rand.Intn(6)+1`? They both seem to work for me, is one of them sub-optimal for some reason? – pzkpfw Jul 30 '19 at 14:56
10

The best way to non-deterministically seed the math/rand generator is with hash/maphash (playground):

package main

import (
    "fmt"
    "hash/maphash"
    "math/rand"
)

func main() {
    r := rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64())))
    fmt.Println(r.Int())
}

Compared to time.Now(), maphash guarantees distinct seeds (even on different machines). Compared to crypto/rand, it is much faster, and is a one-liner.

gp.
  • 141
  • 1
  • 7
10

With Go 1.20 (Q4 2022), the proper way to seed random number generator could also be... to do nothing.

If Seed is not called, the generator will be seeded randomly at program startup.

The proposal "math/rand: seed global generator randomly" is accepted (Oct. 2022), and the implementation has started:

  • CL 443058: math/rand: auto-seed global source

Implement proposal #54880, to automatically seed the global source.

The justification for this not being a breaking change is that any use of the global source in a package's init function or exported API clearly must be valid - that is, if a package changes how much randomness it consumes at init time or in an exported API, that clearly isn't the kind of breaking change that requires issuing a v2 of that package.
That kind of per-package change in the position of the global source is indistinguishable from seeding the global source differently. So if the per-package change is valid, so is auto-seeding.

And then, of course, auto-seeding means that packages will be far less likely to depend on the specific results of the global source and therefore not break when those kinds of per-package changes happen in the future.

Seed(1) can be called in programs that need the old sequence from the global source and want to restore the old behavior.
Of course, those programs will still be broken by the per-package changes just described, and it would be better for them to allocate local sources rather than continue to use the global one.


From issue 20661 and CL 436955, note also that math/rand.Read is deprecated: For almost all use cases, crypto/rand.Read is more appropriate.

As noted here:

One can use gosec linter with golanglint-ci like so and watch for G404 code:

golangci-lint run --disable-all --enable gosec
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
1

I tried the program below and saw different string each time

package main

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

func RandomString(count int){
  rand.Seed(time.Now().UTC().UnixNano()) 
  for(count > 0 ){
    x := Random(65,91)
    fmt.Printf("%c",x)
    count--;
  }
}

func Random(min, max int) (int){
 return min+rand.Intn(max-min) 
}

func main() {
 RandomString(12)
}

And the output on my console is

D:\james\work\gox>go run rand.go
JFBYKAPEBCRC
D:\james\work\gox>go run rand.go
VDUEBIIDFQIB
D:\james\work\gox>go run rand.go
VJYDQPVGRPXM
Meraj al Maksud
  • 1,528
  • 2
  • 22
  • 36
0

@[Denys Séguret] has posted correct. But In my case I need new seed everytime hence below code;

Incase you need quick functions. I use like this.


func RandInt(min, max int) int {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return r.Intn(max-min) + min
}

func RandFloat(min, max float64) float64 {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return min + r.Float64()*(max-min)
}

source

STEEL
  • 8,955
  • 9
  • 67
  • 89
-1

It's nano seconds, what are the chances of getting the same seed twice.
Anyway, thanks for the help, here is my end solution based on all the input.

package main

import (
    "math/rand"
    "time"
)

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

// generates a random string
func srand(min, max int, readable bool) string {

    var length int
    var char string

    if min < max {
        length = min + rand.Intn(max-min)
    } else {
        length = min
    }

    if readable == false {
        char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    } else {
        char = "ABCDEFHJLMNQRTUVWXYZabcefghijkmnopqrtuvwxyz23479"
    }

    buf := make([]byte, length)
    for i := 0; i < length; i++ {
        buf[i] = char[rand.Intn(len(char)-1)]
    }
    return string(buf)
}

// For testing only
func main() {
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, false))
    println(srand(5, 7, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 4, true))
    println(srand(5, 400, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
}
RoboTamer
  • 3,474
  • 2
  • 39
  • 43
  • 1
    re: `what are the chances of getting the exact the exact same [nanosecond] twice?` Excellent. It all depends on the internal precision of the _implementation_ of the golang runtimes. Even though the units are nano-seconds, the smallest increment might a milli-second or even a second. – Jesse Chisholm Jul 22 '17 at 01:17
-1

If your aim is just to generate a sting of random number then I think it's unnecessary to complicate it with multiple function calls or resetting seed every time.

The most important step is to call seed function just once before actually running rand.Init(x). Seed uses the provided seed value to initialize the default Source to a deterministic state. So, It would be suggested to call it once before the actual function call to pseudo-random number generator.

Here is a sample code creating a string of random numbers

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



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

    var s string
    for i:=0;i<10;i++{
    s+=fmt.Sprintf("%d ",rand.Intn(7))
    }
    fmt.Printf(s)
}

The reason I used Sprintf is because it allows simple string formatting.

Also, In rand.Intn(7) Intn returns, as an int, a non-negative pseudo-random number in [0,7).

Captain Levi
  • 804
  • 7
  • 18
-1

Every time the randint() method is called inside the for loop a different seed is set and a sequence is generated according to the time. But as for loop runs fast in your computer in a small time the seed is almost same and a very similar sequence is generated to the past one due to the time. So setting the seed outside the randint() method is enough.

package main

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

var r = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
func main() {
    fmt.Println(randomString(10))
}

func randomString(l int) string {

    var result bytes.Buffer
    var temp string
    for i := 0; i < l; {
        if string(randInt(65, 90)) != temp {
            temp = string(randInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

func randInt(min int, max int) int {
    return min + r.Intn(max-min)
}
0example.com
  • 317
  • 2
  • 4
-3

Small update due to golang api change, please omit .UTC() :

time.Now().UTC().UnixNano() -> time.Now().UnixNano()

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

func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(randomInt(100, 1000))
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}
letanthang
  • 390
  • 5
  • 7