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
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Attackers can predict authentication tokens, session identifiers, or access credentials, bypassing security controls to gain unauthorized access to systems and data. |
| Confidentiality | Scope: Confidentiality Cryptographic keys with insufficient entropy can be brute-forced or predicted, compromising the confidentiality of encrypted data and communications. |
| Integrity | Scope: 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
-
MITRE Corporation. "CWE-331: Insufficient Entropy." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/331.html
-
NIST. "Recommendation for Random Number Generation." SP 800-90A Rev 1. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final
-
Goldberg, I., Wagner, D. "Randomness and the Netscape Browser." Dr. Dobb's Journal, 1996.