Insufficient Entropy

Description

Insufficient Entropy is a vulnerability that occurs when a product uses an algorithm or scheme that produces values with insufficient randomness, leaving patterns or clusters of values that are more likely to occur than others. Entropy is a measure of unpredictability or randomness in a system. When security mechanisms rely on unpredictable values but those values are generated with insufficient entropy, attackers can exploit the patterns to predict or narrow down the possible values. This affects cryptographic keys, session identifiers, authentication tokens, and any security-sensitive values that depend on randomness. Even when using a proper PRNG algorithm, insufficient entropy in the seed or input can render the output predictable.

Risk

Insufficient entropy creates predictable patterns that attackers can exploit to compromise security mechanisms. If session IDs have only 16 bits of effective entropy instead of the intended 128 bits, attackers can enumerate all possible values in seconds rather than millennia. Cryptographic keys with low entropy can be brute-forced even when using strong algorithms. Password reset tokens, CSRF tokens, and API keys become guessable. The risk is often underestimated because the values may appear random through casual observation, but statistical analysis reveals exploitable patterns. Real-world attacks have compromised entire cryptographic systems through entropy weaknesses - the Debian OpenSSL bug that reduced key space to 65,536 possibilities affected thousands of systems. Timing-based seeds, counter-based generation, and other insufficient entropy sources have enabled session hijacking, certificate forgery, and authentication bypass.

Solution

Determine the entropy requirements for your security context and ensure your random value generation meets those requirements. Use cryptographically secure random number generators seeded from high-quality entropy sources like hardware random number generators or operating system entropy pools (/dev/urandom on Linux, CryptGenRandom on Windows). For cryptographic keys, use at least 128 bits of entropy (256 bits recommended for long-term security). For session IDs and tokens, use at least 128 bits of entropy. Never derive entropy solely from predictable sources like timestamps, process IDs, or user-controllable data. Validate entropy sources during testing using statistical analysis tools. Consider using entropy estimation to verify sufficient randomness before using generated values.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Attackers can predict authentication tokens, session identifiers, or access credentials, bypassing security controls to gain unauthorized access to systems and data.
ConfidentialityScope: Confidentiality

Cryptographic keys with insufficient entropy can be brute-forced or predicted, compromising the confidentiality of encrypted data and communications.
IntegrityScope: Integrity

Predictable values in integrity mechanisms like CSRF tokens or message authentication codes allow attackers to forge valid tokens and bypass integrity checks.

Example Code

Vulnerable Code (Python/Java)

The following examples demonstrate insufficient entropy vulnerabilities:

# Vulnerable: Insufficient entropy in random value generation
import random
import time
import os

# Vulnerable: Only using timestamp for seed
def vulnerable_session_id():
    # Vulnerable: time.time() has low entropy (seconds precision)
    # Attacker knowing approximate time can narrow search space
    random.seed(int(time.time()))
    return hex(random.getrandbits(128))[2:]

# Vulnerable: Combining weak entropy sources
def vulnerable_token(user_id):
    # Vulnerable: User ID + timestamp = predictable
    # Both values are knowable by attacker
    seed = user_id ^ int(time.time())
    random.seed(seed)
    return format(random.getrandbits(64), '016x')

# Vulnerable: Counter-based entropy
class VulnerableCounter:
    counter = 0

    @classmethod
    def generate(cls):
        # Vulnerable: Counter provides no entropy
        cls.counter += 1
        return format(cls.counter, '032x')

# Vulnerable: PID + time - very limited entropy
def vulnerable_nonce():
    # Vulnerable: PID range is typically 1-65535
    # Combined with time gives < 32 bits effective entropy
    pid = os.getpid()
    timestamp = int(time.time())
    return f"{pid:05d}{timestamp:010d}"

# Vulnerable: Short seed from password
def vulnerable_key_from_password(password):
    # Vulnerable: Using only first 4 chars of password
    # Dramatically reduces entropy
    seed = sum(ord(c) for c in password[:4])
    random.seed(seed)
    return bytes([random.randint(0, 255) for _ in range(32)])

# Vulnerable: Insufficient bits
def vulnerable_short_token():
    # Vulnerable: Only 16 bits - 65536 possibilities
    random.seed(os.urandom(16))  # Good seed, but...
    return format(random.getrandbits(16), '04x')  # Too short!
// Vulnerable: Insufficient entropy in Java
import java.util.Random;

public class VulnerableEntropy {

    // Vulnerable: Time-based seed only
    public String vulnerableSessionId() {
        // Vulnerable: System.currentTimeMillis() has limited entropy
        // Typically 1000 values per second
        Random rand = new Random(System.currentTimeMillis());
        return Long.toHexString(rand.nextLong());
    }

    // Vulnerable: Combining weak sources
    public String vulnerableToken(int userId) {
        // Vulnerable: Both values predictable
        long seed = userId ^ System.currentTimeMillis();
        Random rand = new Random(seed);
        return Long.toHexString(rand.nextLong());
    }

    // Vulnerable: Insufficient seed bits
    public byte[] vulnerableKey() {
        // Vulnerable: Random() default uses only 48 bits
        Random rand = new Random();
        byte[] key = new byte[32];
        rand.nextBytes(key);
        return key;  // Only 48 bits of entropy despite 256-bit key!
    }

    // Vulnerable: Hash of predictable data
    public String vulnerableHash(String username) throws Exception {
        // Vulnerable: Hash doesn't add entropy
        String data = username + System.currentTimeMillis();
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(data.getBytes());
        return bytesToHex(hash);  // Looks random but predictable!
    }

    // Vulnerable: Sequential with obfuscation
    private long counter = 0;

    public String vulnerableSequential() {
        // Vulnerable: XOR doesn't add entropy
        counter++;
        return Long.toHexString(counter ^ 0xDEADBEEF);
    }
}
// Vulnerable: Insufficient entropy in C
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

// Vulnerable: Time-only seed
void vulnerable_init_random() {
    // Vulnerable: Only ~32 bits of entropy
    srand(time(NULL));
}

// Vulnerable: PID-based entropy
unsigned int vulnerable_seed() {
    // Vulnerable: PID has < 16 bits entropy
    // Time adds maybe 20 bits
    return getpid() ^ time(NULL);
}

// Vulnerable: Stack address entropy
char* vulnerable_address_seed() {
    // Vulnerable: ASLR provides limited entropy
    int stack_var;
    srand((unsigned int)&stack_var);

    static char buffer[33];
    for (int i = 0; i < 32; i++) {
        buffer[i] = "0123456789abcdef"[rand() % 16];
    }
    buffer[32] = '\0';
    return buffer;
}

// Vulnerable: Counter with weak mixing
static unsigned long counter = 0;

unsigned long vulnerable_counter_mix() {
    // Vulnerable: Mathematical operations don't add entropy
    counter++;
    return (counter * 6364136223846793005ULL + 1442695040888963407ULL);
}

// Vulnerable: Insufficient entropy file
void vulnerable_key_from_file(unsigned char *key, int len) {
    // Vulnerable: /dev/random may not have enough entropy
    // Can block or return insufficient data
    FILE *f = fopen("/dev/random", "rb");  // Should use /dev/urandom
    int read = fread(key, 1, len, f);
    // Doesn't check if read < len!
    fclose(f);
}

Fixed Code (Python/Java)

# Fixed: Proper entropy in random value generation
import secrets
import os
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes

# Fixed: System entropy source
def secure_session_id():
    # Fixed: secrets uses OS entropy
    # 256 bits of entropy
    return secrets.token_hex(32)

# Fixed: Sufficient entropy for tokens
def secure_token():
    # Fixed: 128 bits minimum, using 256 for safety
    return secrets.token_urlsafe(32)

# Fixed: Cryptographic key with proper entropy
def secure_generate_key(length=32):
    # Fixed: os.urandom reads from /dev/urandom
    return os.urandom(length)

# Fixed: Derived key with proper entropy
def secure_derived_key(master_key, info, length=32):
    # Fixed: Use HKDF with sufficient master key entropy
    hkdf = HKDF(
        algorithm=hashes.SHA256(),
        length=length,
        salt=os.urandom(16),  # Random salt
        info=info.encode()
    )
    return hkdf.derive(master_key)

# Fixed: Verification code with bounded entropy
def secure_verification_code(digits=6):
    # Fixed: Using secrets.randbelow
    max_value = 10 ** digits
    return format(secrets.randbelow(max_value), f'0{digits}d')

# Fixed: Secure nonce with sufficient bits
def secure_nonce(bits=128):
    # Fixed: Explicit entropy requirement
    byte_length = bits // 8
    return os.urandom(byte_length)

# Fixed: Password-based key with proper entropy stretching
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

def secure_key_from_password(password, salt=None):
    # Fixed: PBKDF2 stretches limited password entropy
    if salt is None:
        salt = os.urandom(16)

    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=600000
    )
    return kdf.derive(password.encode()), salt
// Fixed: Proper entropy in Java
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;

public class SecureEntropy {

    private SecureRandom secureRandom;

    public SecureEntropy() throws Exception {
        // Fixed: Strong SecureRandom instance
        this.secureRandom = SecureRandom.getInstanceStrong();
    }

    // Fixed: Session ID with sufficient entropy
    public String secureSessionId() {
        // Fixed: 256 bits of entropy
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    // Fixed: Token with specified entropy
    public String secureToken(int entropyBits) {
        // Fixed: Explicit entropy requirement
        int bytes = entropyBits / 8;
        byte[] tokenBytes = new byte[bytes];
        secureRandom.nextBytes(tokenBytes);
        return bytesToHex(tokenBytes);
    }

    // Fixed: Cryptographic key with full entropy
    public byte[] secureKey(int keySize) {
        // Fixed: SecureRandom provides full entropy
        byte[] key = new byte[keySize];
        secureRandom.nextBytes(key);
        return key;
    }

    // Fixed: Nonce with explicit entropy
    public byte[] secureNonce(int bits) {
        byte[] nonce = new byte[bits / 8];
        secureRandom.nextBytes(nonce);
        return nonce;
    }

    // Fixed: Password-based key derivation
    public byte[] secureKeyFromPassword(String password, byte[] salt)
            throws Exception {
        // Fixed: PBKDF2 properly handles password entropy
        if (salt == null) {
            salt = new byte[16];
            secureRandom.nextBytes(salt);
        }

        SecretKeyFactory factory = SecretKeyFactory.getInstance(
            "PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(
            password.toCharArray(),
            salt,
            600000,  // High iterations
            256
        );
        return factory.generateSecret(spec).getEncoded();
    }

    // Fixed: Verification code
    public String secureVerificationCode(int digits) {
        // Fixed: Unbiased random number generation
        int max = (int) Math.pow(10, digits);
        int code = secureRandom.nextInt(max);
        return String.format("%0" + digits + "d", code);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
// Fixed: Proper entropy in C
#include <openssl/rand.h>
#include <stdio.h>
#include <string.h>

// Fixed: Using OpenSSL RAND_bytes
int secure_random_bytes(unsigned char *buffer, int len) {
    // Fixed: RAND_bytes uses system entropy
    if (RAND_bytes(buffer, len) != 1) {
        return -1;  // Entropy generation failed
    }
    return 0;
}

// Fixed: Session ID with full entropy
int secure_session_id(char *session_id, size_t max_len) {
    unsigned char bytes[32];  // 256 bits

    // Fixed: Full entropy from CSPRNG
    if (RAND_bytes(bytes, sizeof(bytes)) != 1) {
        return -1;
    }

    // Convert to hex
    for (size_t i = 0; i < sizeof(bytes) && i * 2 < max_len - 1; i++) {
        sprintf(session_id + (i * 2), "%02x", bytes[i]);
    }

    return 0;
}

// Fixed: Using /dev/urandom correctly
int secure_from_urandom(unsigned char *buffer, size_t len) {
    // Fixed: /dev/urandom is non-blocking and suitable for crypto
    FILE *f = fopen("/dev/urandom", "rb");
    if (!f) return -1;

    size_t read = fread(buffer, 1, len, f);
    fclose(f);

    // Fixed: Verify we got all requested bytes
    if (read != len) {
        return -1;
    }

    return 0;
}

// Fixed: Key generation with entropy verification
int secure_generate_key(unsigned char *key, int len) {
    // Fixed: Use RAND_bytes for cryptographic keys
    if (RAND_bytes(key, len) != 1) {
        return -1;
    }

    // Fixed: Optional entropy status check
    if (RAND_status() != 1) {
        // Insufficient entropy in pool
        return -1;
    }

    return 0;
}

// Fixed: Using getrandom() on modern Linux
#ifdef __linux__
#include <sys/random.h>

int secure_getrandom(unsigned char *buffer, size_t len) {
    // Fixed: getrandom() is the preferred syscall
    ssize_t result = getrandom(buffer, len, 0);
    return (result == (ssize_t)len) ? 0 : -1;
}
#endif

The fix uses cryptographically secure sources with sufficient entropy bits for the security context.


Exploited in the Wild

Debian OpenSSL Weak Keys (CVE-2008-0166)

A coding error in Debian's OpenSSL package caused the PRNG to use only the process ID as entropy, generating only 65,536 unique keys across all affected systems.

Session Token Prediction (CVE-2001-0950)

Session tokens generated using C rand() with time-based seeding allowed attackers to predict tokens and hijack sessions.


Tools to Test/Exploit

  • ent — Entropy analysis program for testing random number quality.

  • NIST Statistical Test Suite — Comprehensive randomness testing.

  • untwister — Tool for recovering PRNG states from observed outputs.


CVE Examples

  • CVE-2001-0950 — Session tokens using rand() with insufficient entropy.

  • CVE-2008-2108 — Precision error reducing entropy in random generator.


References

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

  2. NIST. "Recommendation for Random Number Generation." SP 800-90A Rev 1. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final

  3. Goldberg, I., Wagner, D. "Randomness and the Netscape Browser." Dr. Dobb's Journal, 1996.