Math.random() is thought to be “the way” for generating random numbers on the client. It should be sufficient for the common use cases, except for cryptography. The specification leaves a lot of room to browsers on how to implement it, and they do it in quite a few different ways. But sadly their implementations usually leaves a lot of room for improvement.
Problems with Math.random()
The first and most visible problem is that it is not seedable. You can not set it to a known state in order to produce reproducible numbers. For most projects, it’s not a big deal, but as soon as you start writing automated tests, knowing what will come out of it is a must. You can circumvent it by wrapping it to a function that can be replaced when the tests are run, but providing a seed is a much cleaner way.
But the biggest problem is that it provides no guarantees. The spec does not specify the minimum set of requirements you can expect when you use it. It then goes back to the greatest common divisor problem: if any of the supported browser does not support a feature you need, than you can’t use it.
One interesting consequence of bad implementations is a possible deanonimization attack. It is detailed in this paper. Basically the generator is seeded when the browser starts, and by generating a bunch of random numbers, a site can calculate this seed. Because it is different in different browsers, but same for all sites, a user can be linked across different sites.
The moment I realized that I need a different implementation is when I started getting collisions. Generating random UUIDs does not require strong algorithms, but I expect them to be different. Because Math.random() is a black box with magic inside, I could not take a look what’s wrong. By switching to a different generator, the problem disappeared for good.
We write articles like this regularly. Join our mailing list and let's keep in touch.
Better random numbers
After a bit of searching, I found this excellent repository. It provides different PRNG implementations and a common interface for all of them. It has npm support (if you need UMD support too, check out my fork until it gets merged).
To use it, just pull in the Mash.js along with your chosen generator. It’s as easy as this:
If you have no preference, you can choose Alea. It has everything you’ll need from a PRNG.
If you are using ES6 modules, after a simple npm install, you can import it as:
(Note: If you are using the repo without UMD support, you might need to import Mash.js first)
You can pass the constructor a seed and it will generate values based on that.
Please note that it is not a cryptographically secure generator. If you are generating keys, you should use a secure source.