Skip to content

CWE-331: Insufficient Entropy - Java

Overview

Insufficient entropy in Java occurs when using java.util.Random or Math.random() instead of java.security.SecureRandom for cryptographic operations. The Random class is designed for statistical simulations, not security, and produces predictable sequences that can be reproduced if the seed is known.

Primary Defence: Use java.security.SecureRandom for all security-sensitive random value generation including session tokens, encryption keys, and initialization vectors.

Common Vulnerable Patterns

Using Random with time-based seed for tokens

import java.util.Random;

// VULNERABLE - Predictable token generation
public class InsecureTokenGenerator {
    private static final Random random = new Random();

    // VULNERABLE - Time-based seed
    public String generateSessionToken() {
        random.setSeed(System.currentTimeMillis());
        return Long.toHexString(random.nextLong());
    }
}

Why this is vulnerable:

  • Random is deterministic; outputs are predictable from its 48-bit seed.
  • System.currentTimeMillis() makes the seed guessable.

Using Random for encryption keys

import java.util.Random;

// VULNERABLE - Using Random for encryption key
public class InsecureKeyGenerator {
    private static final Random random = new Random();

    public byte[] generateKey() {
        byte[] key = new byte[32];
        random.nextBytes(key);  // Predictable!
        return key;
    }
}

Why this is vulnerable:

  • Keys generated from Random are predictable.
  • Observed outputs can reveal future values.

Using Math.random() for API keys

// VULNERABLE - Math.random() for tokens
public String generateApiKey() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 32; i++) {
        sb.append((char)('A' + (int)(Math.random() * 26)));
    }
    return sb.toString();
}

Why this is vulnerable:

  • Math.random() uses the same predictable PRNG as Random.
  • API keys require CSPRNG-grade entropy.

Using UUID.randomUUID() for security tokens

import java.util.UUID;

// RISKY - UUIDs are for identifiers, not secrets
public String generateResetToken() {
    return UUID.randomUUID().toString();  // Avoid for security tokens
}

Why this is vulnerable:

  • UUIDs are identifiers, not security tokens; entropy and use are implementation-dependent.
  • Password reset tokens should use explicit SecureRandom with sufficient length.

Insufficient entropy in PIN generation

import java.util.Random;

// VULNERABLE - Insufficient entropy (only 4 digits)
public int generatePin() {
    Random random = new Random();
    return random.nextInt(10000);  // 0000-9999
}

Why this is vulnerable:

  • 4 digits is only 10,000 possibilities.
  • Using Random makes the sequence guessable.

Secure Patterns

Using SecureRandom

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

public class SecureTokenGenerator {
    // SECURE - Use SecureRandom (thread-safe, automatically seeded)
    private static final SecureRandom secureRandom = new SecureRandom();

    // Generate cryptographically secure session token (128+ bits)
    public static String generateSessionToken() {
        byte[] token = new byte[16];  // 128 bits
        secureRandom.nextBytes(token);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(token);
    }

    // Generate hex-encoded token
    public static String generateHexToken(int bytes) {
        byte[] token = new byte[bytes];
        secureRandom.nextBytes(token);
        // Java 8/11: replace with a small toHex helper.
        return HexFormat.of().formatHex(token);
    }

    // Generate CSRF token (256 bits)
    public static String generateCsrfToken() {
        byte[] token = new byte[32];  // 256 bits
        secureRandom.nextBytes(token);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(token);
    }

    // Generate API key (384 bits)
    public static String generateApiKey() {
        byte[] key = new byte[48];  // 384 bits
        secureRandom.nextBytes(key);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(key);
    }

    // Generate password reset token (256 bits)
    public static String generatePasswordResetToken() {
        byte[] token = new byte[32];  // 256 bits
        secureRandom.nextBytes(token);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(token);
    }

    // Generate numeric PIN with sufficient entropy
    public static String generateSecurePin(int length) {
        if (length < 6) {
            throw new IllegalArgumentException("PIN must be at least 6 digits");
        }
        StringBuilder pin = new StringBuilder();
        for (int i = 0; i < length; i++) {
            pin.append(secureRandom.nextInt(10));
        }
        return pin.toString();
    }

    // Generate cryptographic key (256 bits for AES-256)
    public static byte[] generateEncryptionKey(int keySize) {
        byte[] key = new byte[keySize / 8];
        secureRandom.nextBytes(key);
        return key;
    }

    // Generate IV for AES encryption
    public static byte[] generateIV() {
        byte[] iv = new byte[16];  // 128 bits for AES
        secureRandom.nextBytes(iv);
        return iv;
    }
}

Why this works:

  • SecureRandom is a CSPRNG seeded from OS entropy sources.
  • Output is not predictably reversible like LCG-based Random.
  • Token sizes (128+ bits) make guessing infeasible.

Complete encryption example with secure randomness

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class SecureEncryption {
    private static final SecureRandom secureRandom = new SecureRandom();
    private static final int GCM_NONCE_LENGTH = 12;  // 96 bits
    private static final int GCM_TAG_LENGTH = 128;   // 128 bits

    // Generate AES-256 key using SecureRandom
    public static SecretKey generateAESKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(256, secureRandom);  // Use SecureRandom for key generation
        return keyGen.generateKey();
    }

    // Generate secure nonce for GCM mode
    public static byte[] generateNonce() {
        byte[] nonce = new byte[GCM_NONCE_LENGTH];
        secureRandom.nextBytes(nonce);
        return nonce;
    }

    // Encrypt with AES-GCM (authenticated encryption)
    public static EncryptedData encrypt(byte[] plaintext, SecretKey key) throws Exception {
        // Generate secure nonce
        byte[] nonce = generateNonce();

        // Create cipher
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec, secureRandom);

        byte[] ciphertext = cipher.doFinal(plaintext);

        return new EncryptedData(nonce, ciphertext);
    }

    // Decrypt AES-GCM ciphertext
    public static byte[] decrypt(EncryptedData encrypted, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, encrypted.nonce);
        cipher.init(Cipher.DECRYPT_MODE, key, spec);

        return cipher.doFinal(encrypted.ciphertext);
    }

    // Data class to hold nonce and ciphertext
    public static class EncryptedData {
        public final byte[] nonce;
        public final byte[] ciphertext;

        public EncryptedData(byte[] nonce, byte[] ciphertext) {
            this.nonce = nonce;
            this.ciphertext = ciphertext;
        }
    }
}

Why this works:

  • Keys and nonces come from SecureRandom, not predictable PRNGs.
  • GCM provides confidentiality and integrity with a 128-bit tag.
  • Nonce size and randomness avoid reuse risk.
// Usage example
SecretKey key = SecureEncryption.generateAESKey();
byte[] plaintext = "sensitive data".getBytes();
EncryptedData encrypted = SecureEncryption.encrypt(plaintext, key);
byte[] decrypted = SecureEncryption.decrypt(encrypted, key);

SecureRandom algorithm selection

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class SecureRandomConfig {
    // Get strongest available SecureRandom instance
    public static SecureRandom getStrongSecureRandom() {
        try {
            // Try to get the strongest algorithm
            return SecureRandom.getInstanceStrong();
        } catch (NoSuchAlgorithmException e) {
            // Fallback to default (still secure)
            return new SecureRandom();
        }
    }

    // Get specific algorithm (platform-dependent)
    public static SecureRandom getSecureRandom(String algorithm) {
        try {
            // Common algorithms:
            // - "NativePRNG" (Unix/Linux - uses /dev/urandom)
            // - "Windows-PRNG" (Windows - uses CryptGenRandom)
            // - "SHA1PRNG" (Cross-platform, but avoid if possible)
            return SecureRandom.getInstance(algorithm);
        } catch (NoSuchAlgorithmException e) {
            return new SecureRandom();  // Fallback to default
        }
    }
}

Why this works:

  • getInstanceStrong() selects the strongest configured CSPRNG.
  • Fallback new SecureRandom() remains cryptographically secure.
  • Avoids weaker or legacy PRNG choices when possible.

Framework-Specific Guidance

Spring Security - Session Management

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import java.security.SecureRandom;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                // Spring Security uses SecureRandom internally for CSRF tokens
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            )
            .sessionManagement(session -> session
                // Spring Session uses SecureRandom for session IDs automatically
                .sessionFixation().newSession()
            );
        return http.build();
    }

    // Custom token generation in Spring
    @Bean
    public SecureRandom secureRandom() {
        return new SecureRandom();
    }
}

// Generate custom tokens in Spring components
import org.springframework.stereotype.Service;
import java.security.SecureRandom;
import java.util.Base64;

@Service
public class TokenService {
    private final SecureRandom secureRandom;

    public TokenService(SecureRandom secureRandom) {
        this.secureRandom = secureRandom;
    }

    public String generateVerificationToken() {
        byte[] token = new byte[32];
        secureRandom.nextBytes(token);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(token);
    }
}

JWT (JSON Web Token) Security

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.security.SecureRandom;

public class JwtTokenProvider {
    // SECURE - Generate signing key with SecureRandom
    private static final Key SIGNING_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    private static final SecureRandom secureRandom = new SecureRandom();

    public String generateJwtToken(String userId) {
        // Generate unique JTI (JWT ID) using SecureRandom
        byte[] jtiBytes = new byte[16];
        secureRandom.nextBytes(jtiBytes);
        String jti = Base64.getUrlEncoder().withoutPadding().encodeToString(jtiBytes);

        return Jwts.builder()
            .setSubject(userId)
            .setId(jti)  // Unique JWT ID
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 3600000))
            .signWith(SIGNING_KEY)
            .compact();
    }
}

Verification

After implementing the recommended secure patterns, verify the fix through multiple approaches:

  • Manual testing: Submit malicious payloads relevant to this vulnerability and confirm they're handled safely without executing unintended operations
  • Code review: Confirm all instances use the secure pattern (parameterized queries, safe APIs, proper encoding) with no string concatenation or unsafe operations
  • Static analysis: Use security scanners to verify no new vulnerabilities exist and the original finding is resolved
  • Regression testing: Ensure legitimate user inputs and application workflows continue to function correctly
  • Edge case validation: Test with special characters, boundary conditions, and unusual inputs to verify proper handling
  • Framework verification: If using a framework or library, confirm the recommended APIs are used correctly according to documentation
  • Authentication/session testing: Verify security controls remain effective and cannot be bypassed (if applicable to the vulnerability type)
  • Rescan: Run the security scanner again to confirm the finding is resolved and no new issues were introduced

Additional Resources