Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Secure random values (in Node.js)

Not all random values are created equal - for security-related code, you need a specific kind of random value.

A summary of this article, if you don't want to read the entire thing:

  • Don't use Math.random(). There are extremely few cases where Math.random() is the right answer. Don't use it, unless you've read this entire article, and determined that it's necessary for your case.
  • Don't use crypto.getRandomBytes directly. While it's a CSPRNG, it's easy to bias the result when 'transforming' it, such that the output becomes more predictable.
  • If you want to generate random tokens or API keys: Use uuid, specifically the uuid.v4() method. Avoid node-uuid - it's not the same package, and doesn't produce reliably secure random values.
  • If you want to generate random numbers in a range: Use random-number-csprng.

You should seriously consider reading the entire article, though - it's not that long :)

Types of "random"

There exist roughly three types of "random":

  • Truly random: Exactly as the name describes. True randomness, to which no pattern or algorithm applies. It's debatable whether this really exists.
  • Unpredictable: Not truly random, but impossible for an attacker to predict. This is what you need for security-related code - it doesn't matter how the data is generated, as long as it can't be guessed.
  • Irregular: This is what most people think of when they think of "random". An example is a game with a background of a star field, where each star is drawn in a "random" position on the screen. This isn't truly random, and it isn't even unpredictable - it just doesn't look like there's a pattern to it, visually.

Irregular data is fast to generate, but utterly worthless for security purposes - even if it doesn't seem like there's a pattern, there is almost always a way for an attacker to predict what the values are going to be. The only realistic usecase for irregular data is things that are represented visually, such as game elements or randomly generated phrases on a joke site.

Unpredictable data is a bit slower to generate, but still fast enough for most cases, and it's sufficiently hard to guess that it will be attacker-resistant. Unpredictable data is provided by what's called a CSPRNG.

Types of RNGs (Random Number Generators)

  • CSPRNG: A Cryptographically Secure Pseudo-Random Number Generator. This is what produces unpredictable data that you need for security purposes.
  • PRNG: A Pseudo-Random Number Generator. This is a broader category that includes CSPRNGs and generators that just return irregular values - in other words, you cannot rely on a PRNG to provide you with unpredictable values.
  • RNG: A Random Number Generator. The meaning of this term depends on the context. Most people use it as an even broader category that includes PRNGs and truly random number generators.

Every random value that you need for security-related purposes (ie. anything where there exists the possibility of an "attacker"), should be generated using a CSPRNG. This includes verification tokens, reset tokens, lottery numbers, API keys, generated passwords, encryption keys, and so on, and so on.

Bias

In Node.js, the most widely available CSPRNG is the crypto.randomBytes function, but you shouldn't use this directly, as it's easy to mess up and "bias" your random values - that is, making it more likely that a specific value or set of values is picked.

A common example of this mistake is using the % modulo operator when you have less than 256 possibilities (since a single byte has 256 possible values). Doing so actually makes lower values more likely to be picked than higher values.

For example, let's say that you have 36 possible random values - 0-9 plus every lowercase letter in a-z. A naive implementation might look something like this:

let randomCharacter = randomByte % 36;

That code is broken and insecure. With the code above, you essentially create the following ranges (all inclusive):

  • 0-35 stays 0-35.
  • 36-71 becomes 0-35.
  • 72-107 becomes 0-35.
  • 108-143 becomes 0-35.
  • 144-179 becomes 0-35.
  • 180-215 becomes 0-35.
  • 216-251 becomes 0-35.
  • 252-255 becomes 0-3.

If you look at the above list of ranges you'll notice that while there are 7 possible values for each randomCharacter between 4 and 35 (inclusive), there are 8 possible values for each randomCharacter between 0 and 3 (inclusive). This means that while there's a 2.64% chance of getting a value between 4 and 35 (inclusive), there's a 3.02% chance of getting a value between 0 and 3 (inclusive).

This kind of difference may look small, but it's an easy and effective way for an attacker to reduce the amount of guesses they need when bruteforcing something. And this is only one way in which you can make your random values insecure, despite them originally coming from a secure random source.

So, how do I obtain random values securely?

In Node.js:

  • If you need individual random numbers in a certain range: use random-number-csprng.
  • If you need API keys or tokens of some sort: use uuid (not node-uuid!), specifically the uuid.v4() method.

Both of these use a CSPRNG, and 'transform' the bytes in an unbiased (ie. secure) way.

@maticrivo

This comment has been minimized.

Copy link

commented Aug 8, 2017

@joepie91 what do you think about this module? https://github.com/ai/nanoid

@matinkaboli

This comment has been minimized.

Copy link

commented Aug 8, 2017

I use stringing for generating strings.

@lauretagabriel

This comment has been minimized.

Copy link

commented Oct 17, 2017

Hi @joepie91,

For API keys or token, are you saying that this code:

var uuidv4 = require('uuid/v4'); var token = uuidv4(); //'110ec58a-a0f2-4ac4-8393-c866d813b8d1'

is more secured than this:

var crypto = require('crypto'); crypto.randomBytes(48, function(crypto_err, buffer) { var token = buffer.toString('hex'); //901fbba5391ed1f6d2d0d160c0af80a8d7bb0496613e9a583e169f00ebb404f413a6486e985d281391fa5b40baed1258 })

@adamkaplan

This comment has been minimized.

Copy link

commented Nov 14, 2017

No, but understanding why it's ok or not ok for your use case does require reading the entire post. Not just the tl;dr...

Don't use crypto.getRandomBytes directly. While it's a CSPRNG, it's easy to bias the result when 'transforming' it, such that the output becomes more predictable.

@dictions

This comment has been minimized.

Copy link

commented Dec 8, 2017

In Node.js:

  • If you need individual random numbers in a certain range: use random-number-csprng.
  • If you need API keys or tokens of some sort: use uuid (not node-uuid!), specifically the uuid.v4() method.
    Both of these use a CSPRNG, and 'transform' the bytes in an unbiased (ie. secure) way.

I'm having a hard time understanding the technical differences between uuid and random-numbercsprng. As an example, if I wanted to encrypt things on disk in a similar way to 1Password, decrypting with a master password, how do these techniques relate to their secret key approach?

@adrian-gierakowski

This comment has been minimized.

Copy link

commented Jan 3, 2018

random-number-csprng is broken for ranges outside unsigned 32 bit int ( at least on nodejs, haven't tested in a browser ). see joepie91/node-random-number-csprng#4

@ChALkeR

This comment has been minimized.

Copy link

commented Apr 10, 2018

Taking nested deps into account:

Query: "node-uuid@
6190711 node-uuid
3766709 log4js
3545797 karma
666033  loggly
519701  phantomjs
451997  domotz-remote-pawn
174327  cluedin-widget
143005  newman
135260  postman-runtime
129591  deathbycaptcha
129307  nativescript
128713  serialised-error
120513  firebase-tools
114116  ember-cli-release
107501  npm-check-updates
@bayotop

This comment has been minimized.

Copy link

commented Apr 26, 2018

I don't believe recommending uuid for token or key generation is the right thing to do. Node has a simple enough API to generate random bytes, as @lauretagabriel pointed out in his comment above. I'm aware that uuid leverages this particular API, but you are essentially including a whole package (which you have no control over) and limiting yourself to 122 bits of randomness instead of writing one line of code. On the top of that, UUIDs are designed to be unique and not random (RFC 4122 says nothing about the quality of the underlying random number generator for version 4).

@bobintornado

This comment has been minimized.

Copy link

commented May 28, 2018

What about method in this blog post? http://dimitri.xyz/random-ints-from-random-bits/

@bennadel

This comment has been minimized.

Copy link

commented Jun 4, 2018

Thanks for the concrete explanation of why the % approach actually does bias the results.

@ottokruse

This comment has been minimized.

Copy link

commented Nov 26, 2018

Okay so I was living in ignorance doing the simple % trick, thank you!

Had some fun with crypto-secure-random-digit - a node module for generating random digits. Zero dependencies and the unit tests do Chi Square test with sample size n=1000000 to test randomness.

@linkdesu

This comment has been minimized.

Copy link

commented Dec 10, 2018

Okay so I was living in ignorance doing the simple % trick, thank you!

Had some fun with crypto-secure-random-digit - a node module for generating random digits. Zero dependencies and the unit tests do Chi Square test with sample size n=1000000 to test randomness.

I dont know too much about cryptography so I dont know if your unit test works, and your unit test will fail sometimes. But it is still impressive! Keep going.

@claudianus

This comment has been minimized.

Copy link

commented Jan 21, 2019

Helped a lot

@serrand

This comment has been minimized.

Copy link

commented Feb 20, 2019

I still don't get how uuidv4() is more secure than crypto.randomBytes(16).toString('hex')?

Who is using the modulo operator on single bytes and why?

@erikeckhardt

This comment has been minimized.

Copy link

commented Feb 28, 2019

Taking nested deps into account:

Query: "node-uuid@
6190711 node-uuid
...

It was specifically called out to use uuid, NOT node-uuid which has been deprecated.

@onury

This comment has been minimized.

Copy link

commented Sep 15, 2019

There are cases where you need to use crypto.randomBytes() and a uuid cannot be used.

For example, you need a fixed-bit key for encryption.
AES-256 requires a 256-bit key.
To generate a random key using hex encoding:

crypto.randomBytes(256 / 8).toString('hex')
// 4313894765b672473ae6afea85087ae3538a3272afb4831a1d3ac97a63d58e37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.