Skip to content

CWE-330: Use of Insufficiently Random Values - Java

Overview

java.util.Random and Math.random() are seeded pseudo-random number generators (PRNGs) whose output is deterministic once the seed is known. The default constructor new Random() seeds from System.nanoTime(), which can be approximated by an attacker who knows when the JVM started or who can send many requests to narrow down the seed value. Given the seed, the attacker can reconstruct all past and predict all future outputs.

This makes these classes unsuitable for security-sensitive values: session tokens, password reset links, API keys, OTP codes, CSRF tokens, or cryptographic nonces. For these purposes, the correct class is java.security.SecureRandom, which sources entropy from the operating system's cryptographic provider (/dev/urandom on Linux, CNG on Windows).

Primary Defence: Replace all new Random() and Math.random() in security contexts with SecureRandom. A single shared SecureRandom instance is thread-safe and can be reused across the application.

Common Vulnerable Patterns

Token Generation with java.util.Random

import java.util.Random;

// VULNERABLE - java.util.Random produces predictable sequences
public class TokenService {
    private static final Random RANDOM = new Random();

    public String generatePasswordResetToken() {
        long token = RANDOM.nextLong();
        return Long.toHexString(token);
    }
}

Why this is vulnerable:

  • Random uses a 48-bit linear congruential generator. Once an attacker observes a single output (e.g., a reset token they requested for their own account), they can derive the seed and predict all other tokens currently in circulation.

OTP with Math.random()

// VULNERABLE - Math.random() uses a shared java.util.Random instance
public int generateOtp() {
    return (int) (Math.random() * 900_000) + 100_000;
}

Why this is vulnerable:

  • Math.random() is backed by java.util.Random. An attacker who can observe a sequence of OTP values can predict the next one, allowing them to bypass SMS-based second factors.

Session ID from Timestamp + Random

// VULNERABLE - seeding from timestamp makes the seed guessable
public String generateSessionId() {
    Random rng = new Random(System.currentTimeMillis()); // predictable seed
    byte[] bytes = new byte[16];
    rng.nextBytes(bytes);
    return HexFormat.of().formatHex(bytes);
}

Why this is vulnerable:

  • Seeding Random from System.currentTimeMillis() makes the seed knowable to anyone who can observe the approximate time of the request. An attacker can enumerate timestamps within a small window to find the seed.

Secure Patterns

256-bit URL-Safe Token

import java.security.SecureRandom;
import java.util.Base64;

public class TokenGenerator {
    // SECURE: SecureRandom is thread-safe; reuse the instance
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    public static String generateToken() {
        byte[] bytes = new byte[32]; // 256 bits
        SECURE_RANDOM.nextBytes(bytes);
        // URL-safe Base64, no padding - safe to use in URLs and headers
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }
}

Why this works:

  • SecureRandom seeds from the OS cryptographic entropy source on first use (/dev/urandom or equivalent). Its output cannot be predicted without knowledge of the OS entropy pool, which is not accessible to application-level attackers.

6-Digit OTP

import java.security.SecureRandom;

public class OtpGenerator {
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    public static int generateOtp() {
        // SECURE: generates a 6-digit code in [100000, 999999]
        return SECURE_RANDOM.nextInt(900_000) + 100_000;
    }
}

Why this works:

  • SecureRandom.nextInt(bound) uses the CSPRNG to generate an unbiased value in [0, bound). Adding 100_000 shifts the range to [100000, 999999] for a 6-digit OTP.

API Key Generation

import java.security.SecureRandom;
import java.util.HexFormat;

public class ApiKeyGenerator {
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    public static String generateApiKey() {
        byte[] keyBytes = new byte[32]; // 256-bit key
        SECURE_RANDOM.nextBytes(keyBytes);
        return HexFormat.of().formatHex(keyBytes); // 64-char hex string
    }

    // Or using the strongly random source (blocks until entropy available - use for key generation only)
    public static String generateMasterKey() throws java.security.NoSuchAlgorithmException {
        SecureRandom strongRng = SecureRandom.getInstanceStrong();
        byte[] keyBytes = new byte[32];
        strongRng.nextBytes(keyBytes);
        return HexFormat.of().formatHex(keyBytes);
    }
}

Why this works:

  • HexFormat.of().formatHex() (Java 17+) provides a clean, standards-compliant hex encoding. SecureRandom.getInstanceStrong() uses the most secure available algorithm (e.g., NativePRNGBlocking on Linux) - suitable for one-time key generation where blocking is acceptable.

Remediation Steps

Locate the Finding

  • Source: Token generation, OTP creation, session ID generation, key generation
  • Sink: new Random(), Math.random(), Random.nextInt() in security-sensitive contexts

Apply the Fix

  • PRIORITY 1: Replace new Random() with new SecureRandom() in all security contexts; make the instance a static final field
  • PRIORITY 2: Replace Math.random() calls in security contexts with SecureRandom.nextBytes() for tokens or SecureRandom.nextInt(bound) for bounded values such as OTPs
  • PRIORITY 3: Search for import java.util.Random across the codebase and audit each usage

Verify the Fix

  • Generate 100 tokens and confirm no two are identical and none follow a pattern
  • Confirm the scanner no longer flags java.util.Random in security contexts
  • Rescan with the security scanner to confirm the finding is resolved

Check for Similar Issues

Search for: import java.util.Random, new Random(, Math.random() in service/token/session classes

Testing

  • Normal input: exercise token, OTP, API-key, and session creation flows that now call SecureRandom.
  • Boundary input: test minimum and maximum token lengths, encoding into URLs or headers, and concurrent generation under load.
  • Malicious input: generate many values around the same timestamp and confirm there is no deterministic sequence or repeated seed behavior.

Additional Resources