Friday, July 12, 2013

Password Generators: Math.random()

When you do a Google search for "password generator", you find a lot of JavaScript-based password generators. Unfortunately, all of the high-ranking ones use Math.random(), which is a pseudorandom number generator unfit for cryptographic use. In Firefox and other browsers, Math.random() is seeded with the time and provides at most 41 bits of entropy. The JavaScript-based generators don't support SSL/TLS connections, either.

In this one, the programmer attempted to generate an integer in the range [0, 25] by multiplying Math.random() by 25 then rounding to the nearest integer. This creates a bias away from 0 and 25. To get a 0 or 25, the unrounded value must fall in the range [0, 0.5) or [24.5, 25] respectively. The width of these ranges is 0.5. For all other results (1 through 24), the width of the range is 1.0. For example, to get a 6, the unrounded value can fall anywhere in [5.5, 6.5).

 for (var i = 0; i < lengthOfPassword; i++) {  
      capitalise = Math.round(Math.random() * 1);  
      if (capitalise === 0) {  
           StrongPasswordArray[i] = theLetters.charAt(Math.round(Math.random() * 25)).toUpperCase();  
      else {  
           StrongPasswordArray[i] = theLetters.charAt(Math.round(Math.random() * 25));  

Lessons Learned:
  • Use a cryptographically secure random number generator when generating a key, password, or initialization vector.
  • Don't use floating point numbers for cryptography.
  • Test the output of your random number generator with tools like diehard.
  • Enforce an SSL/TLS connection if client-side scripts deal with sensitive information.


  1. " Unfortunately, all (...) use Math.random(), which is a pseudorandom number generator unfit for cryptographic use"

    But it's the only PRNG available across browsers. Modern browsers supply you with a cryptographically secure PRNG, but the support for it is coming just now - . There simply was nothing better unfortunately.

    "The JavaScript-based generators don't support SSL/TLS connections, either."
    I don't understand this sentence at all. If you mean that some online password generators are installed over HTTP - fair deal, but I got the impression is about the errors in the libraries/implementors, not just some random websites.

    "Don't use floating point numbers for cryptography."
    Again - there was nothing BUT Math.random() producing floats back then. There wasn't even a concept of a byte array. The fail in the example would go away had user just used Math.floor() instead of Math.round() ( putting aside the case of low entropy that could be mitigated by just using Math.random() multiple times). There's nothing inherently bad with using floats for crypto - it's just easier to shoot yourself in the foot with them (rounding errors, precision limit).

    1. If a platform does not provide a way to get secure random numbers, then it shouldn't be implementing any kind of cryptography, especially not key/password generation.

      There are cross-platform ways to get cryptographically secure random numbers in JavaScript. The easiest is to have the server include some random data from a server-side RNG, then have the JavaScript use a CSPRNG with that seed. There is also the CSPRNG implementation in the SJCL, which is capable of capturing mouse movements as a source of entropy.

      "The JavaScript-based generators don't support SSL/TLS connections, either." I consider this to be a flaw in the implementation, since if the code is being sent in cleartext, there is no basis at all for security.

      Calling Math.random() multiple times does not extract more entropy. The Math.random() state is of fixed size, and seeded from the time, so you can always either brute force the state (41 bits), or guess the time the password was generated (more like 21 bits).

  2. Here's a generator that does it right (gets entropy from mouse movement.)