Insufficient Entropy in PRNG

Description

Insufficient Entropy in PRNG is a vulnerability that occurs when a Pseudo-Random Number Generator (PRNG) lacks sufficient entropy in its seeding or operation, creating both stability and security threats. PRNGs require entropy from external sources to produce unpredictable outputs - without adequate entropy, the PRNG output becomes predictable. This weakness can manifest in two ways: if the PRNG fails closed when entropy is exhausted, the application may hang or crash (availability impact); if it fails open by producing output without sufficient entropy, the generated values become predictable (security impact). The latter case is particularly dangerous as it silently weakens cryptographic operations while appearing to function normally.

Risk

Insufficient PRNG entropy creates critical security vulnerabilities across multiple dimensions. When PRNGs lack entropy during key generation, the resulting cryptographic keys can be predicted or brute-forced. Bitcoin wallets generated with insufficient entropy have been exploited to steal cryptocurrency. Session tokens and authentication cookies become predictable, enabling session hijacking. SSL/TLS connections using weak entropy for key exchange can be broken by attackers who can reconstruct the session keys. The risk is heightened because these failures are often silent - applications continue to produce random-looking output that passes casual inspection but fails under statistical analysis. Systems that exhaust entropy and fall back to weaker sources (like Math.random() instead of a CSPRNG) create particularly dangerous conditions where security silently degrades.

Solution

Use PRNGs that are designed for cryptographic purposes and properly seeded with high-quality entropy. Select FIPS 140-2 compliant random number generators where compliance is required. Implement PRNGs that automatically reseed from hardware entropy sources when available. Use operating system-provided CSPRNGs like /dev/urandom on Unix or CryptGenRandom on Windows which manage entropy pools appropriately. Never fall back to weak random sources when the secure source fails - instead, fail the operation entirely. Monitor entropy levels in critical systems and alert when entropy runs low. Consider hardware random number generators (HRNGs) for high-security applications. Design applications to handle entropy exhaustion gracefully without degrading security.

Common Consequences

ImpactDetails
AvailabilityScope: Availability

When a PRNG exhausts its entropy source and fails closed, applications may pause, block, or crash while waiting for more entropy, causing denial of service.
Access ControlScope: Access Control

If the PRNG fails open when entropy runs out, it produces predictable values that weaken authentication and authorization mechanisms, allowing unauthorized access.
ConfidentialityScope: Confidentiality

Encryption keys generated with insufficient PRNG entropy can be predicted or brute-forced, compromising the confidentiality of encrypted data.

Example Code

Vulnerable Code (Python/Java)

The following examples demonstrate insufficient PRNG entropy:

# Vulnerable: PRNG with insufficient entropy handling
import random
import time

# Vulnerable: Fallback to weak random on failure
def vulnerable_generate_key():
    try:
        import secrets
        return secrets.token_bytes(32)
    except Exception:
        # Vulnerable: Falling back to weak random!
        random.seed(time.time())
        return bytes([random.randint(0, 255) for _ in range(32)])

# Vulnerable: Using random module for crypto
def vulnerable_session_token():
    # Vulnerable: random module is not cryptographically secure
    # Uses Mersenne Twister which is predictable
    return format(random.getrandbits(128), '032x')

# Vulnerable: Seeding with insufficient entropy
def vulnerable_prng_init():
    # Vulnerable: Only 32 bits of entropy from time
    seed = int(time.time())
    random.seed(seed)
    return random

# Vulnerable: PRNG without reseeding
class VulnerableStaticPRNG:
    def __init__(self):
        # Vulnerable: Seeded once at startup
        self.rng = random.Random()
        self.rng.seed(time.time())

    def generate(self, length):
        # Vulnerable: Never reseeds, entropy depletes over time
        return bytes([self.rng.randint(0, 255) for _ in range(length)])

# Vulnerable: Custom PRNG with weak entropy
class VulnerableLCG:
    def __init__(self, seed=None):
        # Vulnerable: Linear Congruential Generator
        # with predictable parameters
        self.state = seed or int(time.time())

    def next(self):
        # Vulnerable: LCG is completely predictable
        self.state = (self.state * 1103515245 + 12345) & 0x7FFFFFFF
        return self.state
// Vulnerable: Insufficient PRNG entropy in Java
import java.util.Random;

public class VulnerablePRNG {

    // Vulnerable: Using java.util.Random for security
    private Random weakRandom = new Random();

    // Vulnerable: Fallback to weak random
    public byte[] vulnerableGenerateKey(int length) {
        byte[] key = new byte[length];
        try {
            java.security.SecureRandom.getInstanceStrong().nextBytes(key);
        } catch (Exception e) {
            // Vulnerable: Falling back to weak random!
            weakRandom.nextBytes(key);
        }
        return key;
    }

    // Vulnerable: Time-seeded Random for tokens
    public String vulnerableToken() {
        // Vulnerable: java.util.Random has only 48 bits seed
        Random r = new Random(System.currentTimeMillis());
        return Long.toHexString(r.nextLong());
    }

    // Vulnerable: Shared Random instance
    private static Random sharedRandom = new Random();

    public byte[] vulnerableSharedRandom(int length) {
        // Vulnerable: Shared instance, predictable after observation
        byte[] result = new byte[length];
        sharedRandom.nextBytes(result);
        return result;
    }

    // Vulnerable: Math.random() for security
    public String vulnerableMathRandom() {
        // Vulnerable: Math.random() is not cryptographic
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 32; i++) {
            sb.append(String.format("%02x", (int)(Math.random() * 256)));
        }
        return sb.toString();
    }
}
// Vulnerable: Insufficient PRNG entropy in JavaScript
// This pattern was found in real cryptocurrency libraries

// Vulnerable: Using Math.random() for crypto
function vulnerableGenerateKey(length) {
    const key = new Uint8Array(length);
    for (let i = 0; i < length; i++) {
        // Vulnerable: Math.random() is not cryptographically secure
        key[i] = Math.floor(Math.random() * 256);
    }
    return key;
}

// Vulnerable: Fallback to Math.random()
function vulnerableSecureRandom(length) {
    try {
        const crypto = require('crypto');
        return crypto.randomBytes(length);
    } catch (e) {
        // Vulnerable: Silent fallback to weak random!
        console.warn('Using fallback random');
        const result = Buffer.alloc(length);
        for (let i = 0; i < length; i++) {
            result[i] = Math.floor(Math.random() * 256);
        }
        return result;
    }
}

// Vulnerable: Seeded PRNG for wallet generation
function vulnerableWalletSeed() {
    // Vulnerable: Using timestamp as only entropy source
    const seed = Date.now();
    return customPRNG(seed);
}

// Vulnerable: XorShift without proper seeding
class VulnerableXorShift {
    constructor(seed) {
        // Vulnerable: Weak seeding
        this.state = seed || Date.now();
    }

    next() {
        // Vulnerable: Predictable given state
        let x = this.state;
        x ^= x << 13;
        x ^= x >> 17;
        x ^= x << 5;
        this.state = x;
        return x >>> 0;
    }
}

Fixed Code (Python/Java)

# Fixed: Proper PRNG entropy handling
import os
import secrets
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# Fixed: Always use secure random, fail on error
def secure_generate_key(length=32):
    # Fixed: Use secrets module which uses system CSPRNG
    # Will raise exception if entropy unavailable
    return secrets.token_bytes(length)

# Fixed: Proper session token generation
def secure_session_token():
    # Fixed: secrets.token_hex uses CSPRNG
    return secrets.token_hex(32)

# Fixed: No fallback to weak random
def secure_random_bytes(length):
    # Fixed: os.urandom is the system CSPRNG
    # Raises exception rather than degrading security
    return os.urandom(length)

# Fixed: PRNG with proper seeding and reseeding
class SecurePRNG:
    def __init__(self):
        # Fixed: Initialize from system entropy
        self.reseed()

    def reseed(self):
        # Fixed: Get fresh entropy from system
        self._state = os.urandom(32)

    def generate(self, length):
        # Fixed: Use AES-CTR for DRBG behavior
        nonce = os.urandom(16)
        cipher = Cipher(algorithms.AES(self._state), modes.CTR(nonce))
        encryptor = cipher.encryptor()

        # Generate random bytes
        result = encryptor.update(bytes(length))

        # Fixed: Reseed periodically
        self.reseed()

        return result

# Fixed: Entropy monitoring
def secure_with_entropy_check():
    # Fixed: On Linux, can check entropy pool
    try:
        with open('/proc/sys/kernel/random/entropy_avail', 'r') as f:
            entropy = int(f.read().strip())
            if entropy < 256:
                # Wait or alert, don't proceed with weak entropy
                raise RuntimeError(f"Insufficient entropy: {entropy}")
    except FileNotFoundError:
        pass  # Not on Linux, rely on system

    return os.urandom(32)
// Fixed: Proper PRNG entropy handling in Java
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;

public class SecurePRNG {

    private SecureRandom secureRandom;

    public SecurePRNG() throws NoSuchAlgorithmException {
        // Fixed: Use strong SecureRandom
        this.secureRandom = SecureRandom.getInstanceStrong();
    }

    // Fixed: Always use SecureRandom, fail on error
    public byte[] secureGenerateKey(int length) throws NoSuchAlgorithmException {
        byte[] key = new byte[length];
        // Fixed: No fallback - fail if entropy unavailable
        secureRandom.nextBytes(key);
        return key;
    }

    // Fixed: Secure token generation
    public String secureToken() {
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return bytesToHex(bytes);
    }

    // Fixed: Force reseed for long-running applications
    public void reseed() {
        // Fixed: generateSeed gets fresh system entropy
        byte[] seed = secureRandom.generateSeed(32);
        secureRandom.setSeed(seed);
    }

    // Fixed: Thread-safe secure random
    public byte[] threadSafeRandom(int length) {
        // Fixed: ThreadLocalRandom for SecureRandom
        byte[] result = new byte[length];
        SecureRandom.getInstanceStrong().nextBytes(result);
        return result;
    }

    // Fixed: DRBG-based SecureRandom (Java 9+)
    public SecureRandom getDRBGRandom() throws NoSuchAlgorithmException {
        // Fixed: Use DRBG mechanism with reseeding
        return SecureRandom.getInstance("DRBG",
            DrbgParameters.instantiation(256,
                DrbgParameters.Capability.RESEED_ONLY,
                null));
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
// Fixed: Proper PRNG entropy in JavaScript
const crypto = require('crypto');

// Fixed: Use crypto.randomBytes, fail on unavailable
function secureGenerateKey(length) {
    // Fixed: crypto.randomBytes uses system CSPRNG
    // Throws error if entropy unavailable
    return crypto.randomBytes(length);
}

// Fixed: No fallback to Math.random()
function secureRandom(length) {
    // Fixed: Fail completely rather than degrade
    if (!crypto || !crypto.randomBytes) {
        throw new Error('Secure random not available');
    }
    return crypto.randomBytes(length);
}

// Fixed: Browser-compatible secure random
function browserSecureRandom(length) {
    // Fixed: Use Web Crypto API
    if (typeof window !== 'undefined' && window.crypto) {
        const array = new Uint8Array(length);
        window.crypto.getRandomValues(array);
        return array;
    }
    throw new Error('Secure random not available in this environment');
}

// Fixed: Secure wallet seed generation
function secureWalletSeed() {
    // Fixed: Use proper entropy source
    const entropy = crypto.randomBytes(32);
    // Additional entropy from system if available
    const timestamp = Buffer.from(Date.now().toString());
    const combined = crypto.createHash('sha256')
        .update(entropy)
        .update(timestamp)
        .digest();
    return combined;
}

// Fixed: Async secure random with explicit entropy check
async function secureRandomAsync(length) {
    return new Promise((resolve, reject) => {
        crypto.randomBytes(length, (err, buffer) => {
            if (err) {
                reject(new Error('Entropy generation failed: ' + err.message));
            } else {
                resolve(buffer);
            }
        });
    });
}

The fix uses cryptographically secure PRNGs with proper entropy sources and fails safely when entropy is insufficient.


Exploited in the Wild

JavaScript Cryptocurrency Library (CVE-2019-1715)

A JavaScript cryptocurrency library fell back to insecure Math.random() instead of reporting failure when secure random was unavailable, enabling generation of non-unique Bitcoin wallet keys and theft of funds.

Android SecureRandom Vulnerability (2013)

Android's Java SecureRandom implementation had insufficient entropy on some devices, leading to Bitcoin wallet compromises where the same keys were generated on different devices.


Tools to Test/Exploit


CVE Examples


References

  1. MITRE Corporation. "CWE-332: Insufficient Entropy in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/332.html

  2. NIST. "Recommendation for Random Bit Generator (RBG) Constructions." SP 800-90C. https://csrc.nist.gov/publications/detail/sp/800-90c/draft

  3. FIPS 140-2. "Security Requirements for Cryptographic Modules." https://csrc.nist.gov/publications/detail/fips/140/2/final