The math/rand package now automatically seeds the global random number
generator (used by top-level functions like Float64 and Int) with a random
value, and the top-level Seed function has been deprecated. Programs that
need a reproducible sequence of random numbers should prefer to allocate
their own random source, using rand.New(rand.NewSource(seed)).
We've had some truly nasty bugs from people who weren't familiar with the prior behavior of having a default seed of zero for the global random-number generator. This is going to save many people so much heartache.
> We've had some truly nasty bugs from people who weren't familiar with the prior behavior of having a default seed of zero for the global random-number generator. This is going to save many people so much heartache.
Agreed, but worth nothing that this does not make it cryptographically secure. The output can still be predicted. For cryptographic security, you need crypto/rand: https://pkg.go.dev/crypto/rand
In general, my advice is to use the cryptographically secure RNG unless you specifically know you need reproducibility (e.g. scientific simulation, or map generation in video games). With non-secure RNGs, it's very easy to accidentally expose yourself to problems without realizing it.
> The whole point of math/rand is that it's not cryptographically secure.
Sure, and that's even been in the documentation since before Go 1.0, yet people still make mistakes with it in practice. I've found it worth making the point explicit. Particularly in a case like this, where a casual reader might not notice the distinction between "non-reproducible" and "secure".
If you, as a developer who doesn't know much about random numbers or cryptography, think you need a random value, and you don't know if it needs to be cryptographically secure or not, you may as well just use a cryptographic RNG interface (unless you're using so much that performance or entropy becomes an issue.)
I think in most cases, it's pretty benign if you use cryptographic randomness even when it's not necessary. But, if you use math/rand when you wanted cryptographic randomness, to generate IDs or some such, that would be a much worse outcome.
Maybe it's bad for someone to use an RNG without understanding this well enough, though, and they should instead use a higher level abstraction if at all possible. But I can get behind the general idea.
> If you, as a developer who doesn't know much about random numbers or cryptography, think you need a random value, and you don't know if it needs to be cryptographically secure or not, you may as well just use a cryptographic RNG interface (unless you're using so much that performance or entropy becomes an issue.) I think in most cases, it's pretty benign if you use cryptographic randomness even when it's not necessary. But, if you use math/rand when you wanted cryptographic randomness, to generate IDs or some such, that would be a much worse outcome.
This is more or less what I was getting at. The main two downsides to using crypto/rand are:
- ergonomics (crypto/rand is a less user-friendly interface than math/rand)
- concurrent performance
The first one can be easily solved with a wrapper[0]. The second is particularly relevant here, because the main distinguishing feature of the global RNG in math/rand is that it is safe for concurrent use, whereas user-instantiated RNGs in math/rand are not. The big downside to this is that it's very easy to end up with performance issues due to mutex contention when multiple packages all use the global RNG (which is common in practice).
I actually submitted a CL (patch) to fix the mutex contention issue in the global RNG about five years ago, but it was rejected on the grounds that callers might depend on the specific sequence of numbers with the default seed, which would arguably break the compatibility promise. That apparently is no longer a concern (this change breaks the same thing, and the notes in the CL justify it), so I might resubmit it now.
crypto/rand is a little less performant in the single-threaded case, but not much - I think it'd be rare for that to be the bottleneck in real-life workloads at scale. The mutex, on the other hand, is a common bottleneck - I've run into this multiple times in multiple different codebases, one of which is what motivated the aforementioned CL.
So I generally advise people to use crypto/rand unless they are certain they need reproducibility, because the potential downside of accidentally using a non-secure RNG when you actually need one is quite high[1], but the downside of using a threadsafe cryptographically-secure one when you needed a threadsafe non-secure one is quite low: you're already taking much of the performance hit because of the mandated mutex, so the number of use cases that actually require the global RNG is quite small.
[1] there are a number of places where RNGs end up being used that don't obviously result in exploits but nevertheless result in exploits in practice. For the average developer, it's easiest just to avoid that quagmire altogether, rather than try to reason about the potential adversaries (and potentially get that wrong).
> OK, but a good sign that you're not using a cryptographic RNG is that you're somehow "seeding" it.
The change here is specific to the global RNG, which users often used without explicitly seeding - e.g. calling rand.Int() without first calling the now-deprecated rand.Seed(int64).
The distinction is obvious to people who have domain expertise here, but I've found many people make mistakes with it in practice, because it's easy to do.
That's not quite right. There is such a thing as a CSPRNG (https://en.wikipedia.org/wiki/Cryptographically_secure_pseud...). But you still have to seed it with another source of random (ideally non-computational) to actually get the "cryptographically secure" bit.
What? Cryptographic RNGs can be seeded, this is done all the time. Being able to seed a random number generator has no bearing on its cryptographic security.
As examples of secure cryptographic RNGs that can be seeded:
- Fortuna (has a seedable generator without the entropy pool parts)
The system feeds unpredictable bits into its kernel random number generator and then expands it, through /dev/urandom and getrandom. You, as a developer, shouldn't be messing with any of this. (Obviously: Keccak, AES, and ChaCha are not themselves CSPRNGs at all, but rather primitives cryptography engineers can use to create them).
If you're seeding an RNG, you're almost certainly working with a userland RNG, which is, almost always, a mistake.
I think this is more about interfaces than algorithms. A good cryptographic RNG interface will generally not expose the ability to explicitly set the seed for a given algorithm. Instead it would either abstract this entirely away, or provide some kind of interface for adding entropy to an entropy pool. The PRNGs themselves obviously do need to have seeds...
Hear me out, I think in its deprecated-but-not-removed state it is actually more dangerous.
Projects who have been seeding the random generator like they should suddenly think “oh I don’t need to do that anymore” and get rid of their manual seeding.
Then a compromised or rogue library decides to seed the global generator themselves to a hard coded value in an `init()`, thus meaning merely importing the library re-statics the seed.
It would look pretty innocuous and non-obvious in code AND be potentially pretty difficult to notice it happening in a lot of use cases. For bonus points/making it slightly harder to detect points they could even have a random set of seeds they use.
The right answer, probably just generally anyway, is to never use the global generator, and always create your own instance. Global state is a danger once again
I think that's an interesting case, but any program where the random numbers have security implications should already be using crypto/rand, and not math/rand anyway.
I remember when I started programming this was one of the first quirks that really surprised me, I can just hear myself exclaiming "This is meant to be random! Why do I keep getting the same result?!"
Because almost everyone who asks for a random number expects a random number. Reusing a predictable seed _by default_ violates that totally reasonable expectation.
(BTW, I'm deliberately avoiding making any distinction between so-called "true random" and PRNG above, because the difference isn't actually meaningful here!)
I'd say the functions are commonly misnamed. The "secure random" function should be the "random" function, and the "PRNG based on a user-provided seed" should be called something else. Maybe "MonteCarlo", since that's one of the common uses.
Insane seems to me an obviously an intentionally dramatic word choice of GP, but it seems clear to me that the answer is “because subsequent runs the same program would produce the same sequence of random numbers.” This violates my personal intuition and assumptions about a programming language’s standard lib RNG behavior.
Not a Go programmer though but that was my understanding.
In the go std lib, there's an explicit difference between a cryptographically secure number generator and a pseudo random number generator. Their names alone reflects the difference very well: "crypt/rand" vs "math/rand" and the difference and properties of each is well documented.
Personally I've wrote a whole bunch of unit tests in the past that relies on that repeatability property "math/rand" has over "crypt/rand"
There's many uses for random numbers where cryptographic random is unnecessary, but where different runs of the program should use different pseudo random sequences.
Of course, but in those cases, you should make sure that your initial seeds are random. It seems like in the older Go cases, the initial seed was fixed. But, if you wanted random numbers, you could have used your own (truly random) seed.
I don't really see an issue with either default, so long as it's documented and you can use a fixed seed if you'd like. I personally like needing to set a random seed explicitly. But then again, I learned about using random number generators a long time ago when this was always required, so what I think is obvious is probably a surprise to many (as shown by this stdlib change).
The only downside about this new change was that in the old way, if you needed a cryptographically secure random number, you had to explicitly call those functions from the stdlib. The choice should have been deliberate, but people don't like to read documentation...
If you randomly generate numbers using `math/rand`, then every invocation of your program is going to see the same sequence of "random" numbers. If you happen to persist those random numbers and expect a uniform distribution, you're going to be sadly surprised in the future.
It's the kind of "bug" that doesn't manifest until your data is thoroughly boned.
> So, if I use `math/rand` the RNG will always output the same sequence of random numbers?
math/rand provides global functions using a global RNG, as well as the ability to instantiate your own RNG and call functions (methods) on that RNG.
Previously, the global functions all used a seed of 1, which made them generate identical sequences. Now, they use random seed, which makes them less-easily predictable (though still predictable).
There is no change to the self-instantiated RNGs.
> How do I make sure I'm passing a random seed to my RNG?
With the change, using the global functions in Go 1.20+ is the same as instantiating your own RNG with a random seed.
The example in previous versions of the Go math/rand package suggested using time.Now().UnixNano() to seed the RNG source since that's essentially a random int64.
What a fascinating insight to the naming conventions of the different OS.
Mac the long descriptive. BSD short and accurate. Linux short details are for specs. Fuchisa the functional HW reference ? What is that
There are ways to get truly random numbers into a computer. Certain phenomena are theorized to be fundamentally random, and can be used as inputs to hardware.
The sound of conviction in your first sentence does not match the "theorized to be" in the second sentence. I recommend that you don't bring a "for all intents and purposes" to a "philosophically" fight. ;)
Or perhaps the universe is a simulation whose prng was seeded with 1 just like golang does
Maybe we're in the first batch of simulations, and the tester came along and asks why they're all identical. The cosmic coder then realises that they forgot to call the function to seed the prng.
Is it though? I’m merely a layperson here so I might be grossly misunderstanding, but I didn’t know determinism had been ruled out by quantum physics. I was under the impression that quantum phenomena was best described using probability. That means there might be an element of true randomness going on, but also that these systems are so chaotic that an observation is going to have some noise, regardless of any randomness at the core. The latter says nothing about how random things are, merely that they appear with some probability, they could be completely deterministic for all we know.
The generator returns the same sequence every time. For instance Kubernetes CSI drivers have a supposedly unique csiProvisionerIdentity but it uses math/rand without seeding so they're all using the integer number 8081.
Having the seed value set to the same value at run time will cause the pseudorandom number generator to produce the same sequence of values. In essence just creating a new random number generator in go w/o setting the seed will not actually generate random values.
> Which is also significant because the global RNG is safe for concurrent use. Instantiated RNGs are not.
Amusingly, I submitted a patch for this some years ago, and it was rejected on the grounds that callers might depend on the specific sequence of numbers outputted by the global RNG using the default seed, which could break the compatibility promise.
Now that that's been deemed a non-issue, I might resubmit.
It appears like it's an API design that intersects the world of high level application development and computer science.
Given randomness requires a seed and UNIX's ISO standard defines the default seed as 0, it's rational from a comp sci perspective to expect to supply a seed to produce a random number.
However, due to how ergonomic and safe high level languages are today, you don't need to be a computer scientist to make a highly available, beautiful and robust application. As such, the inhabitants of the application development world are considerably less concerned with such details and want, when they call a thing, it gives an intuitive result.
It's important that tool makers consider their users when creating APIs. Supplying a seed to a function called "getRandomNumber" might be obvious to a C programmer, but not obvious to a JavaScript programmer looking to use Go to improve the performance of their back end service.
How does the seed value get created randomly? I presume it doesn't use the same global random number generator, since that would still make it deterministic?