Blog ยป Cryptography ยป Secure Random Numbers in Node.js
fantasy painting of gamblers generating random numbers with dice

Secure Random Numbers in Node.js

By Lane Wagner on Jul 3, 2019

Curated backend podcasts, videos and articles. All free.

Want to improve your backend development skills? Subscribe to get a copy of The Beat in your inbox each month. It's a newsletter packed with the best content for new backend devs.

Quick answer: use crypto.randomBytes() for cryptographically secure randomness in Node.js.

const { randomBytes } = await import('node:crypto');

const buf = randomBytes(256);
console.log(`${buf.length} bytes of random data: ${buf.toString('hex')}`);

crypto.randomBytes() is a cryptographically secure random number generator based on openssl. Depending on the operating system of the user, randomBytes will use /dev/urandom (Unix) or CryptoGenRandom (Windows).

While still pseudo-random sources, the important thing is that they are not guessable by an attacker. In other words, after using crypto.randomBytes() to generate a secret key for AES-256 encryption, no one will be able to guess the key.

Randomness is a hard problem in Node.js ๐Ÿ”—

Randomness is a hard problem for computers. Most functions that generate randomness in Node.js are not considered cryptographically secure. As a result, it’s possible for attackers to take a good guess at which number will be generated. In the case of guessing a private key, insecure randomness can be actually be catastrophic.

How to generate insecure random numbers ๐Ÿ”—

Math.random() a JavaScript built-in function that returns a pseudo-random number between 0 and 1. At first this sounds fairly pointless, but by always generating a number between 0 and 1, the user of the function can scale that random result up to whatever size they need.

Insecure example ๐Ÿ”—

Generate an insecure random number between 0 and 10 in Node.js:

const betweenOneAndTen = Math.floor(Math.random() * 10)

Insecure example #2 ๐Ÿ”—

Generate an insecure random number between 10 and 100 in Node.js:

const min = 10
const max = 100
const betweenOneAndTen = Math.floor(Math.random() * (max - min)) + min + 1

Why is Math.Random() insecure? ๐Ÿ”—

Many non-secure sources of entropy, like Math.Random(), do something similar to the following:

function getRandom(timestamp, maxNumber){
  // Take the deterministic hash of the timestamp
  const hashedTime = sha256(timestamp)
  // Reduce the hash to within the range [0, maxNumber)
  return hashedTime % maxNumber

This function (while ignoring some implementation details of modulus math by such a large number) will return random numbers that are based on a timestamp input, which is called the seed. If I pass in different timestamps, the corresponding outputs would appear random. This is an example of a weak pseudo-random number generator.

A weak pseudo-random number generator works perfectly fine if you’re trying to:

  • Create sample data for an application
  • Write a video game engine
  • etc …

However, weak pseudo-randomness can be catastrophically dangerous if you’re trying to:

  • Generate Bitcoin keys
  • Generate passwords or salts
  • etc …

Should I always use crypto.randomBytes()? ๐Ÿ”—

No. There are dangers if you implement your random number generator on top of a low-level API like random bytes. Because it returns bytes and not numbers, it’s up to you to convert the bytes into numbers. If you make a mistake, it can result in a vulnerability in your system.

In short, use crypto.randomBytes() whenever you need raw bytes. If you need a number within a range, for example, a random number between 0 and 9, then use a non-biased function that uses crypto.randomBytes() as the source of entropy. For example: node-random-number-csprng

Find a problem with this article?

Report an issue on GitHub