Don't use Math.random()

March 17, 2022

Don't use Math.random()

Once in a while, you'll find the need to generate some secrets for your users on the browser. It might seem tempting to write a one-liner like Math.random().toString(36).slice(2) and call it a day. The generated strings might seem random but are not cryptographically secure. I'm sure you are not surprised.

In most browsers, Math.random() is implemented using a pseudo-random number generator (PRNG). This means that the random number is derived from an internal state, mangled by a deterministic algorithm for every new random number. It seems random to the user because the algorithm is tuned in such a way that it appears so. However, if you know the generator's internal state, you know all the future numbers generated by it.

About Math.random

As the ECMAScript specifications don't define the algorithm for a Math.random implementation, developers building browsers can choose the implementation they see fit. Ideally, the algorithm should use as little memory as possible and be quick to perform while having a significant period length.

The period length of a PRNG is the number of output bits after which the algorithm begins to repeat itself.

Most modern browsers today use the xorshift128+. (Read why Chrome V8 switched to this algorithm). It is one of the fastest non-cryptographically-secure random number generators. You can read more about the implementation of Math.random() in this article.

These algorithms, including the xorshift can be broken — here is an article about it. The internet has seen many instances where older implementations have been susceptible to simple prediction attacks, the most popular one I know from my college gaming days is from CSGOJackpot. Here's more to read on that. More such stories such as this and this tell us why using Math.random() is not a great idea. While these hacks may not be exploitable today, it does surface the weakness of PRNGs to be used in this context. A deterministic algorithm can and will be broken.

The Web Cryptography API has got you covered

In 2017, World Wide Web Consortium (W3C) presented the Web Cryptography API. It provides a set of cryptographic primitives that can perform basic operations such as hashing, signature generation and verification, and encryption and decryption. Additionally, it describes an API for applications to generate and manage the keying material necessary to perform these operations. Read more on MDN.

All popular browsers provide an implementation of the Web Crypto API to JavaScript applications through the semi-global crypto object. For generating secrets, the getRandomValues method is all we need. The method is widely supported and generates cryptographically secure random numbers. It takes in a typedArray as an input and returns the same array, with its contents replaced with the newly generated random numbers.

crypto.getRandomValues(new Uint8Array(10))
// Uint8Array(10) [137, 32, 62, 244, 157, 240, 103, 42, 47, 57]

Implementations don't use a truly random number generator to guarantee enough performance. Instead, they use a pseudo-random number generator seeded with a value with enough entropy. This ensures there's a good balance between performance and the quality of the randomness generated. Since the seed value is random, the output is cryptographically secure.

While the methods and primitives provided by this API are inherently secure, it's easy to use them wrong. While using it, ensure your work is reviewed thoroughly by someone knowledgeable in this subject.

Using the API to generate secrets

Step 1: Generating the seed

The seed is a Uint8 Array that is filled with random numbers using getRandomValues. Uint8Array generates an array of 8-bit unsigned integers.

generateSeed() {
  return window.crypto.getRandomValues(new Uint8Array(256));
}

Step 2: Looping over the seed

We loop over the generated seed until the character length is satisfied. If we've exhausted the seed before that, the seed is generated again. We use String.fromCharCode to generate a character inside the loop. If it is valid, it is appended to the secret string returned at the end of the function.

generate(length = 32): string {
  let secret = "";
  let randomSeed;

  while (secret.length < length) {
    randomSeed = generateSeed()
    for (let ii = 0; ii < randomSeed.length; ii++) {
      const char = String.fromCharCode(randomSeed[ii]);

      // Append the character to secret if it is valid
      if (validateCharacter(char)) {
        secret += char;
      }

      if (secret.length === length) {
        break;
      }
    }
  }

  return secret;
}

The generated character will always belong to the ASCII set. The validate function simply checks if it belongs to the ASCII printable characters, i.e., letters, digits, punctuation marks, and a few miscellaneous symbols.

Introducing Shifty

Sometimes the browser may not support the web crypto API. In such cases, developers have no other choice but to fall back on Math.random. Recently, we published a library that takes care of this in a tiny package. It's called Shifty.

expand on that

Shifty is a tiny zero-dependency secrets generator built for the web using TypeScript. Shifty is made for the browser and won't work with Node. You can use the built-in crypto module instead.

It provides a straightforward API to generate a secret of any length. To start using shifty, just install it using the package manager of your choice.

yarn add @deepsource/shifty

Using Shifty is quite straightforward. Just initialize the Shifty class with the defaults and use the generate method to generate secrets of any length. Shifty falls back to using Math.random if the crypto object is not present on window. It shows a warning on the developer console when using the fallback method.

import Shifty from '@deepsource/shifty'

const shifty = new Shifty((harden = true), (defaultLength = 16))
shifty.generate((length = 12)) // G8qZt7PEha^s

Shifty is just a single TypeScript file. You can peek into the source code here. Give it a star if you like it!

Get started with DeepSource

DeepSource is free forever for small teams and open-source projects. Start analyzing your code in less than 2 minutes.

Choose an account
Newsletter

Read product updates, company announcements, how we build DeepSource, what we think about good code, and more.