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:
Randomuses 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 byjava.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
RandomfromSystem.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:
SecureRandomseeds from the OS cryptographic entropy source on first use (/dev/urandomor 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). Adding100_000shifts 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.,NativePRNGBlockingon 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()withnew SecureRandom()in all security contexts; make the instance astatic finalfield - PRIORITY 2: Replace
Math.random()calls in security contexts withSecureRandom.nextBytes()for tokens orSecureRandom.nextInt(bound)for bounded values such as OTPs - PRIORITY 3: Search for
import java.util.Randomacross 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.Randomin 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.