HeoLab
ToolsBlogAboutContact
HeoLab

Free developer tools with AI enhancement. Built for developers who ship.

Tools

  • JSON Formatter
  • JWT Decoder
  • Base64 Encoder
  • Timestamp Converter
  • Regex Tester
  • All Tools →

Resources

  • Blog
  • What is JSON?
  • JWT Deep Dive
  • Base64 Explained

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 HeoLab. All rights reserved.

Tools work in your browser. Zero data retention.

HomeBlogWhy Math.random() Is Dangerous for Security (And What to Use Instead)
Table of Contents▾
  • The Illusion of Randomness
  • How Math.random() Works
  • The Vulnerability in Practice
  • The Correct Alternative: Web Crypto API
  • Crypto.randomUUID()
  • When Math.random() Is Fine
  • Quick Reference
  • Conclusion
security#security#randomness#cryptography

Why Math.random() Is Dangerous for Security (And What to Use Instead)

Math.random() is not cryptographically secure — its output is predictable if you know the seed. Learn about entropy, CSPRNGs, and how to generate secure random values in JavaScript.

Trong Ngo
March 3, 2026
4 min read

The Illusion of Randomness

Math.random() feels random. The numbers look unpredictable. But computers are deterministic machines — they cannot generate true randomness. Instead, they use pseudo-random number generators (PRNGs): algorithms that take a "seed" (initial state) and produce a sequence that looks random.

The problem: if an attacker can determine the seed or observe enough output, they can predict all future values. For Math.random(), this is easier than you think.

How Math.random() Works

Most JavaScript engines implement Math.random() using xorshift128+ (V8, SpiderMonkey) or xoshiro256++ (JSC). These are fast PRNGs optimized for statistical quality — not security.

// V8 xorshift128+ implementation (simplified)
let state0 = /* seeded from system entropy at startup */
let state1 = /* seeded from system entropy at startup */

function mathRandom() {
  let s1 = state0
  let s0 = state1
  state0 = s0
  s1 ^= s1 << 23
  s1 ^= s1 >> 17
  s1 ^= s0
  s1 ^= s0 >> 26
  state1 = s1
  return (state0 + state1) / 2**64
}

In 2016, researchers showed that observing just 3 consecutive Math.random() outputs is sufficient to recover the internal 128-bit state of V8's PRNG — allowing prediction of all future and past values.

The Vulnerability in Practice

// ❌ Dangerous: token generated with Math.random()
function generateToken() {
  return Math.random().toString(36).slice(2) + Date.now().toString(36)
}

// An attacker who can observe 3 tokens from your server can predict all future tokens
// — including password reset links, session IDs, and CSRF tokens

The Correct Alternative: Web Crypto API

The crypto.getRandomValues() API uses the operating system's CSPRNG (Cryptographically Secure PRNG) — on Linux this is /dev/urandom (BLAKE2-based entropy pool), on macOS/iOS it's the SecRandomCopyBytes API. These are designed specifically to be unpredictable.

// ✅ Cryptographically secure random bytes
const bytes = new Uint8Array(32)
crypto.getRandomValues(bytes)
// [45, 182, 23, 88, ...] — 32 unpredictable bytes

// ✅ Secure random integer in range [0, max)
function secureRandomInt(max) {
  const limit = 2**32 - (2**32 % max)   // Avoid modulo bias
  let value
  do {
    const arr = new Uint32Array(1)
    crypto.getRandomValues(arr)
    value = arr[0]
  } while (value >= limit)
  return value % max
}

// ✅ Secure random token (hex)
function secureToken(byteLength = 32) {
  const bytes = new Uint8Array(byteLength)
  crypto.getRandomValues(bytes)
  return Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("")
}
// "a3f8c21b9e047d6..." (64 hex chars = 32 bytes = 256 bits)

// ✅ Secure random token (base64url)
function secureTokenB64(byteLength = 32) {
  const bytes = new Uint8Array(byteLength)
  crypto.getRandomValues(bytes)
  return btoa(String.fromCharCode(...bytes))
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
}

Crypto.randomUUID()

// ✅ Built-in secure UUID v4 generation (Node 19+, modern browsers)
crypto.randomUUID()
// "3b1b1a4c-5a9f-4d0e-8d7a-2c1f3e9b0a1d"

// This is equivalent to:
function manualUUID() {
  const bytes = new Uint8Array(16)
  crypto.getRandomValues(bytes)
  bytes[6] = (bytes[6] & 0x0f) | 0x40  // version 4
  bytes[8] = (bytes[8] & 0x3f) | 0x80  // variant
  const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("")
  return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`
}

When Math.random() Is Fine

Math.random() is perfectly appropriate for non-security use cases:

  • Game mechanics and simulations
  • UI animations and visual randomness
  • A/B test assignment (when predictability isn't a security risk)
  • Shuffling display order
  • Random sample selection for non-sensitive data
// ✅ Fine: random card shuffle in a game
function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));  // OK here
    [array[i], array[j]] = [array[j], array[i]]
  }
  return array
}

// ❌ Not fine: shuffling cryptographic keys, generating tokens, passwords

Quick Reference

Use CaseAPI
Password reset tokenscrypto.getRandomValues()
Session IDscrypto.getRandomValues()
CSRF tokenscrypto.getRandomValues()
UUID generationcrypto.randomUUID()
Passphrases / random stringscrypto.getRandomValues()
Game randomnessMath.random() ✓
UI animationsMath.random() ✓

All HeoLab security tools — Random String Generator, Passphrase Generator, Password Generator — use crypto.getRandomValues() exclusively.

Conclusion

Math.random() is a statistical tool, not a security tool. Its internal state is recoverable from a small number of observations, making any token, password, or ID generated from it potentially predictable. Use crypto.getRandomValues() for anything security-sensitive — it is equally fast, available everywhere, and genuinely unpredictable.

Try These Tools

Random String Generator

Generate random strings with configurable length, count, and character set — hex, alphanumeric, base64url, and more.

UUID Generator

Generate UUIDs v4 and v7 in bulk. Supports multiple formats: standard, no-dashes, braces, and URN.

Passphrase Generator

Generate secure, memorable passphrases from a word list. Shows entropy bits and strength rating.

Password Generator

Generate cryptographically secure passwords with custom length, character sets, and strength analysis.

Related Articles

JavaScript's Intl API: Working with Time Zones, Dates, and Locales

4 min read

Passphrases vs Passwords: Why Four Random Words Beat Random Characters

4 min read

Understanding CORS: Why Browsers Block Your Requests and How to Fix It

4 min read

Back to Blog

Table of Contents

  • The Illusion of Randomness
  • How Math.random() Works
  • The Vulnerability in Practice
  • The Correct Alternative: Web Crypto API
  • Crypto.randomUUID()
  • When Math.random() Is Fine
  • Quick Reference
  • Conclusion

Related Articles

JavaScript's Intl API: Working with Time Zones, Dates, and Locales

4 min read

Passphrases vs Passwords: Why Four Random Words Beat Random Characters

4 min read

Understanding CORS: Why Browsers Block Your Requests and How to Fix It

4 min read