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
| Impact | Details |
|---|---|
| Availability | Scope: 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 Control | Scope: Access Control If the PRNG fails open when entropy runs out, it produces predictable values that weaken authentication and authorization mechanisms, allowing unauthorized access. |
| Confidentiality | Scope: 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
-
ent — Statistical entropy analysis tool.
-
NIST SP 800-90B Entropy Estimation — Official NIST entropy assessment tools.
-
rngtest — Random number generator testing tool.
CVE Examples
-
CVE-2019-1715 — Cryptocurrency library fallback to Math.random().
-
CVE-2013-ANDROID — Android SecureRandom insufficient entropy.
References
-
MITRE Corporation. "CWE-332: Insufficient Entropy in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/332.html
-
NIST. "Recommendation for Random Bit Generator (RBG) Constructions." SP 800-90C. https://csrc.nist.gov/publications/detail/sp/800-90c/draft
-
FIPS 140-2. "Security Requirements for Cryptographic Modules." https://csrc.nist.gov/publications/detail/fips/140/2/final