Predictable Seed in Pseudo-Random Number Generator (PRNG)
Description
Predictable Seed in Pseudo-Random Number Generator (PRNG) is a vulnerability that occurs when a PRNG is initialized with a seed value that an attacker can predict or determine. Common predictable seed sources include system time (often with second precision), process IDs, thread IDs, memory addresses, or combinations of these values. Since PRNGs produce deterministic output based on their seed, a predictable seed dramatically reduces the search space an attacker must explore to predict generated values. Instead of searching an astronomical number of possible random sequences, attackers need only consider the relatively small set of possible seed values.
Risk
Predictable seeds transform theoretical security into practical vulnerability. System time seeds typically reduce the seed space to thousands or millions of values (seconds or milliseconds in a time window), compared to the theoretical 2^128 or more possible values. Process IDs are typically 15-16 bits (32,768 to 65,536 values). When attackers can estimate when a seed was generated (application startup, session creation, etc.), they can enumerate all possible seeds in seconds or minutes. Real-world exploits have used predictable seeds to compromise cryptocurrency wallets (CVE-2020-7010), predict session tokens, forge authentication codes, and break encryption. The Debian OpenSSL bug (CVE-2008-0166) where only process ID was used for seeding affected thousands of systems and remains one of the most impactful cryptographic vulnerabilities in history.
Solution
Seed PRNGs exclusively from cryptographically secure, unpredictable entropy sources. Use operating system-provided CSPRNGs (/dev/urandom, CryptGenRandom, SecureRandom.getInstanceStrong()) that gather entropy from hardware sources and system events. Never derive seeds from system time, process IDs, memory addresses, or other predictable system state. If you must use a statistical PRNG for performance reasons, seed it from a CSPRNG with at least 256 bits of entropy. Implement defense in depth by combining multiple independent entropy sources when available. Consider hardware random number generators for high-security applications. Test for predictable seeding by auditing initialization code and using static analysis tools.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Attackers can predict authentication tokens, session IDs, and passwords generated with predictably-seeded PRNGs, bypassing authentication and gaining unauthorized access. |
| Confidentiality | Scope: Confidentiality Cryptographic keys generated from predictable seeds can be reconstructed through seed enumeration, compromising encrypted data. |
| Integrity | Scope: Integrity CSRF tokens, nonces, and other integrity mechanisms become predictable and forgeable when based on predictably-seeded PRNGs. |
Example Code
Vulnerable Code (Python/Java)
The following examples demonstrate predictable seed vulnerabilities:
# Vulnerable: Predictable PRNG seeds
import random
import time
import os
# Vulnerable: Current time as seed
def vulnerable_time_seed():
# Vulnerable: time.time() is predictable
# Attacker knowing approximate time narrows to thousands of values
random.seed(time.time())
return random.getrandbits(128)
# Vulnerable: Process ID as seed
def vulnerable_pid_seed():
# Vulnerable: PID is only 15-16 bits
# Enumerable in milliseconds
random.seed(os.getpid())
return random.getrandbits(64)
# Vulnerable: Timestamp from request
def vulnerable_request_time(request_timestamp):
# Vulnerable: Attacker controls or knows timestamp
random.seed(int(request_timestamp))
return format(random.getrandbits(64), '016x')
# Vulnerable: Microsecond time (slightly better but still weak)
def vulnerable_microsecond_seed():
# Vulnerable: Still only ~20 bits of entropy per second
random.seed(int(time.time() * 1000000))
return random.random()
# Vulnerable: Combining weak sources
def vulnerable_combined_seed():
# Vulnerable: XOR/combination of weak sources is still weak
# Total entropy is bounded by weakest link
seed = int(time.time()) ^ os.getpid() ^ os.getppid()
random.seed(seed)
return random.randint(0, 2**64)
# Vulnerable: Memory address seed
def vulnerable_address_seed():
# Vulnerable: Address space is limited, ASLR provides ~28 bits
obj = object()
random.seed(id(obj))
return random.getrandbits(64)
# Vulnerable: User input in seed
def vulnerable_user_seed(user_id, timestamp):
# Vulnerable: Both values are known/predictable
random.seed(user_id * 1000000 + int(timestamp))
return format(random.getrandbits(64), '016x')
# Vulnerable: Counter-based seed
class VulnerableCounterSeed:
counter = 0
@classmethod
def generate(cls):
cls.counter += 1
# Vulnerable: Counter is entirely predictable
random.seed(cls.counter)
return random.getrandbits(64)
// Vulnerable: Predictable PRNG seeds in Java
import java.util.Random;
public class VulnerablePredictableSeed {
// Vulnerable: System.currentTimeMillis() seed
public long vulnerableTimeSeed() {
// Vulnerable: ~1000 values per second to enumerate
Random rand = new Random(System.currentTimeMillis());
return rand.nextLong();
}
// Vulnerable: System.nanoTime() seed
public long vulnerableNanoSeed() {
// Vulnerable: Still predictable within time window
Random rand = new Random(System.nanoTime());
return rand.nextLong();
}
// Vulnerable: Thread ID seed
public long vulnerableThreadSeed() {
// Vulnerable: Thread IDs are predictable
Random rand = new Random(Thread.currentThread().getId());
return rand.nextLong();
}
// Vulnerable: Hashcode-based seed
public long vulnerableHashSeed(Object obj) {
// Vulnerable: Hashcode is deterministic
Random rand = new Random(obj.hashCode());
return rand.nextLong();
}
// Vulnerable: Combined predictable sources
public long vulnerableCombinedSeed() {
// Vulnerable: Combination of weak sources
long seed = System.currentTimeMillis() ^
Thread.currentThread().getId() ^
Runtime.getRuntime().freeMemory();
return new Random(seed).nextLong();
}
// Vulnerable: Deployment time seed (real CVE pattern)
private static final long DEPLOY_TIME = System.currentTimeMillis();
public String vulnerableDeploySeed() {
// Vulnerable: Deployment time is discoverable
// All instances deployed together use same seed
Random rand = new Random(DEPLOY_TIME);
return Long.toHexString(rand.nextLong());
}
// Vulnerable: User session seed
public long vulnerableSessionSeed(long userId, long loginTime) {
// Vulnerable: Both values are known
Random rand = new Random(userId ^ loginTime);
return rand.nextLong();
}
}
// Vulnerable: Predictable PRNG seeds in C
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
// Vulnerable: time(NULL) seed
void vulnerable_time_seed() {
// Vulnerable: Only changes once per second
srand(time(NULL));
}
// Vulnerable: PID seed
void vulnerable_pid_seed() {
// Vulnerable: PID is 15-16 bits
srand(getpid());
}
// Vulnerable: Thread ID seed
void vulnerable_thread_seed() {
// Vulnerable: Thread ID is predictable
srand((unsigned int)pthread_self());
}
// Vulnerable: Time + PID (still weak)
void vulnerable_time_pid_seed() {
// Vulnerable: Combined but still limited entropy
// ~32 bits total at best
srand(time(NULL) ^ getpid());
}
// Vulnerable: Address-based seed
void vulnerable_stack_seed() {
// Vulnerable: ASLR provides limited entropy
int stack_var;
srand((unsigned int)&stack_var);
}
// Vulnerable: Clock ticks seed
void vulnerable_clock_seed() {
// Vulnerable: Predictable based on process lifetime
srand(clock());
}
// Vulnerable: gettimeofday seed
void vulnerable_gettimeofday_seed() {
struct timeval tv;
gettimeofday(&tv, NULL);
// Vulnerable: Microseconds still predictable in time window
srand(tv.tv_sec * 1000000 + tv.tv_usec);
}
// Vulnerable: Combined weak sources (router CVE pattern)
void vulnerable_router_seed() {
// Vulnerable: Pattern from CVE-2016-10180
srand(time(0)); // Router PIN generation
}
Fixed Code (Python/Java)
# Fixed: Unpredictable PRNG seeds
import secrets
import os
# Fixed: Use secrets module - no manual seeding needed
def secure_token():
return secrets.token_hex(32)
# Fixed: Secure seed from system entropy
def secure_seeded_random():
import random
gen = random.Random()
# Fixed: 256 bits from system entropy
gen.seed(os.urandom(32))
return gen
# Fixed: For request handling - use CSPRNG
def secure_request_token():
# Fixed: Independent of request timing
return secrets.token_urlsafe(32)
# Fixed: High-precision timing not needed
def secure_random_value():
# Fixed: Use proper entropy source
return secrets.randbits(128)
# Fixed: Proper combination of entropy
def secure_combined_entropy():
# Fixed: Start with strong entropy, can add more
import hashlib
base_entropy = os.urandom(32)
# Additional entropy sources (optional, not required)
import time
additional = str(time.time_ns()).encode()
# Hash combination ensures at least base entropy
combined = hashlib.sha256(base_entropy + additional).digest()
return combined
# Fixed: Memory-safe random
def secure_memory_safe():
# Fixed: Don't use memory addresses
return os.urandom(32)
# Fixed: User-independent random
def secure_user_token(user_id):
# Fixed: User ID doesn't affect entropy
del user_id # Not used for seeding
return secrets.token_hex(32)
# Fixed: Counter doesn't affect entropy
class SecureCounter:
def __init__(self):
self.counter = 0
def generate(self):
self.counter += 1
# Fixed: Counter is just an ID, not a seed
token = secrets.token_hex(16)
return f"{self.counter}:{token}"
// Fixed: Unpredictable PRNG seeds in Java
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public class SecurePredictableSeed {
// Fixed: Use SecureRandom which seeds from system entropy
public long secureRandomLong() throws NoSuchAlgorithmException {
SecureRandom sr = SecureRandom.getInstanceStrong();
return sr.nextLong();
}
// Fixed: No time-based seeding
public byte[] secureToken(int length) throws NoSuchAlgorithmException {
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] token = new byte[length];
sr.nextBytes(token);
return token;
}
// Fixed: Thread-safe secure random
private static final ThreadLocal<SecureRandom> threadLocalRandom =
ThreadLocal.withInitial(() -> {
try {
return SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
});
public long secureThreadRandom() {
return threadLocalRandom.get().nextLong();
}
// Fixed: No object-based seeding
public byte[] secureObjectToken(int length) throws NoSuchAlgorithmException {
// Fixed: Object doesn't affect seeding
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] token = new byte[length];
sr.nextBytes(token);
return token;
}
// Fixed: Instance creation time doesn't matter
public class SecureGenerator {
private final SecureRandom secureRandom;
public SecureGenerator() throws NoSuchAlgorithmException {
// Fixed: SecureRandom handles seeding properly
this.secureRandom = SecureRandom.getInstanceStrong();
}
public String generateToken() {
byte[] bytes = new byte[32];
secureRandom.nextBytes(bytes);
return bytesToHex(bytes);
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
// Fixed: Session independent of user/time
public byte[] secureSessionToken() throws NoSuchAlgorithmException {
// Fixed: User/login time not used for seeding
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] token = new byte[32];
sr.nextBytes(token);
return token;
}
}
// Fixed: Unpredictable PRNG seeds in C
#include <openssl/rand.h>
#include <fcntl.h>
#include <unistd.h>
// Fixed: Seed from /dev/urandom
int secure_init() {
unsigned char seed[32];
int fd = open("/dev/urandom", O_RDONLY);
if (fd < 0) return -1;
if (read(fd, seed, sizeof(seed)) != sizeof(seed)) {
close(fd);
return -1;
}
close(fd);
RAND_seed(seed, sizeof(seed));
OPENSSL_cleanse(seed, sizeof(seed));
return 0;
}
// Fixed: Use RAND_bytes
int secure_random_bytes(unsigned char *buffer, size_t length) {
return RAND_bytes(buffer, length) == 1 ? 0 : -1;
}
// Fixed: No time-based seeding
int secure_token(unsigned char *token, size_t length) {
// Fixed: Time not used
return secure_random_bytes(token, length);
}
// Fixed: No PID seeding
int secure_session_id(unsigned char *session_id, size_t length) {
// Fixed: PID not used
return secure_random_bytes(session_id, length);
}
// Fixed: Using getrandom on Linux
#ifdef __linux__
#include <sys/random.h>
int secure_getrandom(unsigned char *buffer, size_t length) {
ssize_t result = getrandom(buffer, length, 0);
return result == (ssize_t)length ? 0 : -1;
}
#endif
// Fixed: Proper router random (fixing CVE pattern)
int secure_router_pin(char *pin, size_t max_len) {
unsigned char random_bytes[4];
if (secure_random_bytes(random_bytes, sizeof(random_bytes)) != 0) {
return -1;
}
// Convert to PIN using random bytes, not time
unsigned int pin_value =
(random_bytes[0] << 24) |
(random_bytes[1] << 16) |
(random_bytes[2] << 8) |
random_bytes[3];
snprintf(pin, max_len, "%08u", pin_value % 100000000);
return 0;
}
The fix uses system entropy sources rather than predictable values for seeding.
Exploited in the Wild
Debian OpenSSL (CVE-2008-0166)
The Debian OpenSSL package used only process ID for PRNG seeding, generating only 65,536 unique keys across all affected systems.
Kubernetes Cloud App (CVE-2020-7010)
A Kubernetes cloud application generated passwords using an RNG seeded with deployment time, allowing prediction of credentials.
Tools to Test/Exploit
-
untwister — Recovers PRNG seeds from observed outputs.
-
Static analysis tools — Detect time/PID-based seeding patterns.
-
Seed enumeration scripts — Enumerate possible seeds in time windows.
CVE Examples
-
CVE-2008-0166 — Debian OpenSSL PID-only seeding.
-
CVE-2016-10180 — Router PIN using rand(time(0)).
-
CVE-2018-9057 — Cloud product using time-seeded non-crypto PRNG.
References
-
MITRE Corporation. "CWE-337: Predictable Seed in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/337.html
-
Goldberg, I., Wagner, D. "Randomness and the Netscape Browser." Dr. Dobb's Journal, 1996.
-
NIST. "Recommendation for Random Number Generation." SP 800-90A. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final