437 words, 3 min read

Generating random values sounds simple, until you need randomness that is actually secure.

A lot of JavaScript developers reach for Math.random() out of habit. While that works fine for visual effects, games, or non-critical IDs, it should never be used for anything security-sensitive.

That’s where the Web Crypto API comes in.

The problem with Math.random()

Math.random() is not cryptographically secure.

Its output is deterministic and predictable enough that an attacker may be able to reproduce or guess generated values under certain conditions.

That makes it unsuitable for:

  • Session tokens
  • Password reset links
  • API keys
  • CSRF tokens
  • Encryption keys
  • Secure identifiers

Example:

const id = Math.random().toString(36).slice(2)

This may look random, but it is not secure.

Using crypto.getRandomValues()

Modern browsers provide a secure random number generator through the Web Crypto API.

Example:

const bytes = new Uint8Array(16)
crypto.getRandomValues(bytes)
console.log(bytes)

This fills the typed array with cryptographically secure random bytes provided by the operating system.

Generating a secure random token

A common use case is generating secure tokens or identifiers.

function generateToken(length = 32) {
const bytes = new Uint8Array(length)
crypto.getRandomValues(bytes)
return Array.from(bytes)
.map(byte => byte.toString(16).padStart(2, "0"))
.join("")
}
console.log(generateToken())

Example output:

4f8b7d3a1f9e0c8d7a2b6c5d4e3f1a9c

This is significantly safer than using Math.random().

Typed arrays are required

crypto.getRandomValues() only works with integer-based typed arrays.

Supported examples include:

new Uint8Array(32)
new Uint16Array(16)
new Int32Array(8)

This will fail:

crypto.getRandomValues([])

Because regular JavaScript arrays are not supported.

Browser and runtime support

crypto.getRandomValues() is widely supported in modern browsers.

It is also available in runtimes like:

  • Node.js
  • Deno
  • Bun

Example in Node.js:

const bytes = new Uint8Array(16)
crypto.getRandomValues(bytes)
console.log(bytes)

In older Node.js versions, developers typically used:

require("crypto").randomBytes(16)

UUID generation

If your goal is generating UUIDs, modern runtimes also support:

crypto.randomUUID()

Example:

550e8400-e29b-41d4-a716-446655440000

Internally, this also uses cryptographically secure randomness.

Things to remember

  • Use Math.random() for non-security-related randomness only
  • Use crypto.getRandomValues() for anything security-sensitive
  • Prefer crypto.randomUUID() when generating UUIDs
  • Always generate randomness using the operating system’s secure RNG

For modern JavaScript applications, crypto.getRandomValues() should be the default choice whenever security matters.