Why is Math.random() not designed to be cryptographically secure?

I was one of the implementers of JScript and on the ECMA committee in the mid to late 1990s, so I can provide some historical perspective here.

The JavaScript Math.random() function is designed to return a floating point value between 0 and 1. It is widely known (or at least should be) that the output is not cryptographically secure

First off: the design of many RNG APIs is horrible. The fact that the .NET Random class can trivially be misused in multiple ways to produce long sequences of the same number is awful. An API where the natural way to use it is also the wrong way is a "pit of failure" API; we want our APIs to be pits of success, where the natural way and the right way are the same.

I think it is fair to say that if we knew then what we know now, the JS random API would be different. Even simple things like changing the name to "pseudorandom" would help, because as you note, in some cases the implementation details matter. At an architectural level, there are good reasons why you want random() to be a factory that returns an object representing a random or pseudo-random sequence, rather than simply returning numbers. And so on. Lessons learned.

Second, let's remember what the fundamental design purpose of JS was in the 1990s. Make the monkey dance when you move the mouse. We thought of inline expression scripts as normal, we thought of two-to-ten line script blocks as common, and the notion that someone might write a hundred lines of script on a page was really very unusual. I remember the first time I saw a ten thousand line JS program and my first question to the people who were asking me for help because it was so slow compared to their C++ version was some version of "are you insane?! 10KLOC JS?!"

The notion that anyone would need crypto randomness in JS was similarly insane. You need your monkey movements to be crypto strength unpredictable? Unlikely.

Also, remember that it was the mid 1990s. If you were not there for it, I can tell you it was a very different world than today as far as crypto was concerned... See export of cryptography.

I would not have even considered putting crypto strength randomness into anything that shipped with the browser without getting a huge amount of legal advice from the MSLegal team. I didn't want to touch crypto with a ten foot pole in a world where shipping code was considered exporting munitions to enemies of the state. This sounds crazy from today's perspective, but that was the world that was.

why do browsers not replace it with a CSPRNG?

Browser authors do not have to provide a reason to NOT do a change. Changes cost money, and they take away effort from better changes; every change has a huge opportunity cost.

Rather, you have to provide an argument not just why making the change is a good idea, but why it is the best possible use of their time. This is a small-bang-for-the-buck change.

I understand that it could not be re-defined as a CSPRNG as the standard explicitly gives no guarantee for suitability for cryptographic use, but there seems to be no downside to doing it anyway

The downside is that developers are still in a situation where they cannot reliably know whether their randomness is crypto strength or not, and can even more easily fall into the trap of relying on a property that is not guaranteed by the standard. The proposed change doesn't actually fix the problem, which is a design problem.


Because there actually is a cryptographically secure alternative to Math.random():

window.crypto.getRandomValues(typedArray)

This allows the developer to use the right tool for the job. If you want to generate pretty pictures or loot drops for your game, use the fast Math.random(). When you need cryptographically secure random numbers, use the more expensive window.crypto.


JavaScript (JS) was invented in 1995.

  1. Potentially illegal: cryptography was still under tight export control in 1995, so a good CSPRNG might not even have been legal to distribute in a browser.
  2. Performance: historically, CSPRNGs (cryptographically secure pseudo-random number generators) are much slower than PRNGs, so why use a CSPRNG by default?
  3. No security mindset: in 1995, SSL had just been invented. Virtually no servers supported it yet, the internet consisted of phone lines and it was used for public forums (BBSes) and MUDs. Encryption was something for intelligence agencies.
  4. No need: web applications did not exist yet because JavaScript was just being invented. It was designed as an interpreted (thus slow) language to aid pages in being more dynamic. It wouldn't have crossed anyone's mind to use a slow (and barely-existent) CSPRNG by default for a random function.
  5. So little need, in fact, that there was no alternative: JavaScript did not even have a generally supported API for a CSPRNG until December 2013, so proper cryptography in web applications was hardly possible until a few years ago.
  6. Consistency: rather than changing an existing function to have a different meaning, they made a new function with a different name. You can access the CSPRNG now through crypto.getRandomValues.

In summary: legacy, but also speed and consistency. Insecure PRNGs are still much faster because you can't assume all hardware has AES support, nor depend on the availability or security of RDRAND.

Personally, I think it's time to swap out all random functions with CSPRNGs and rename the faster, insecure functions to something like fast_insecure_random(). They should only be needed by scientists or other people who do simulations that need lots of random numbers, but where predictability of the RNG is not an issue. But for a function with two decades of history, where an alternative has existed for only four years now (in 2018), I can understand why we are not at that point just yet.