Incorrect Usage of Seeds in Pseudo-Random Number Generator (PRNG)
Description
Incorrect Usage of Seeds in Pseudo-Random Number Generator (PRNG) is a vulnerability that occurs when a product uses a PRNG but fails to properly manage the seed values. Since PRNGs are deterministic algorithms that produce a sequence of numbers based entirely on an initial seed value, the security of the generated numbers depends critically on the seed's unpredictability and secrecy. Incorrect seed usage includes using predictable values (like timestamps or process IDs), reusing seeds across sessions or instances, exposing seeds through logs or errors, or using insufficient entropy for the seed. When seeds are compromised or predictable, attackers can determine the entire sequence of generated values.
Risk
Incorrect PRNG seeding undermines all security mechanisms that rely on random values. If an attacker can determine or predict the seed, they can compute the exact sequence of "random" numbers the PRNG will produce, enabling prediction of session tokens, encryption keys, authentication challenges, and other security-critical values. Using system time as a seed reduces the search space dramatically - an attacker who knows roughly when a seed was created may need to try only thousands or millions of values instead of the theoretical space of 2^128 or more. Seeds derived from process IDs (typically 16-bit values) are similarly constrained. Real-world attacks have exploited predictable seeding to compromise cryptocurrency wallets, hijack sessions, and break encryption.
Solution
Seed PRNGs with cryptographically secure, unpredictable values obtained from proper entropy sources. Use operating system-provided CSPRNGs like /dev/urandom, CryptGenRandom, or SecureRandom.getInstanceStrong(). Treat seeds as cryptographic secrets - never log them, expose them in error messages, or transmit them. Use at least 256 bits of entropy for seeds. Implement automatic reseeding from high-quality entropy sources periodically. Never derive seeds solely from predictable values like timestamps, process IDs, or user-controlled input. If multiple PRNG instances are needed, each should have independently generated seeds. Consider using FIPS 140-2 compliant random number generation for security-critical applications.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Predictable or exposed seeds allow attackers to compute authentication tokens, session IDs, and access credentials, bypassing authentication and authorization controls. |
| Confidentiality | Scope: Confidentiality Encryption keys generated from predictable seeds can be reconstructed by attackers, compromising the confidentiality of all data encrypted with those keys. |
| Integrity | Scope: Integrity Predictable seeds in integrity mechanisms like CSRF tokens or nonces allow attackers to forge valid tokens and bypass integrity verification. |
Example Code
Vulnerable Code (Python/Java)
The following examples demonstrate incorrect PRNG seed usage:
# Vulnerable: Incorrect PRNG seeding
import random
import time
import os
# Vulnerable: Time-based seed
def vulnerable_time_seed():
# Vulnerable: Current time is predictable
# Only ~31 bits of entropy, easily guessable
random.seed(time.time())
return random.randint(0, 2**64)
# Vulnerable: Process ID seed
def vulnerable_pid_seed():
# Vulnerable: PID is only 15-16 bits
random.seed(os.getpid())
return random.getrandbits(128)
# Vulnerable: User-controlled seed
def vulnerable_user_seed(user_input):
# Vulnerable: Attacker controls the seed
random.seed(hash(user_input))
return random.getrandbits(64)
# Vulnerable: Seed exposed in logs
def vulnerable_logged_seed():
seed = int(time.time() * 1000)
print(f"Initializing with seed: {seed}") # Vulnerable: Logs seed!
random.seed(seed)
return random.getrandbits(128)
# Vulnerable: Constant seed
def vulnerable_constant_seed():
# Vulnerable: Same output every run
random.seed(12345)
return random.getrandbits(64)
# Vulnerable: Insufficient seed entropy
def vulnerable_short_seed():
# Vulnerable: Only 32 bits of entropy
seed = os.urandom(4) # Should be at least 32 bytes
random.seed(int.from_bytes(seed, 'big'))
return random.getrandbits(256)
# Vulnerable: Seed from hostname/IP
def vulnerable_network_seed():
import socket
# Vulnerable: Hostname is predictable
hostname = socket.gethostname()
random.seed(hash(hostname))
return random.getrandbits(64)
// Vulnerable: Incorrect PRNG seeding in Java
import java.util.Random;
public class VulnerableSeeding {
// Vulnerable: Time-based seed
public long vulnerableTimeSeed() {
// Vulnerable: System.currentTimeMillis() is predictable
Random rand = new Random(System.currentTimeMillis());
return rand.nextLong();
}
// Vulnerable: Nano time seed
public long vulnerableNanoSeed() {
// Vulnerable: Still predictable, just more precise
Random rand = new Random(System.nanoTime());
return rand.nextLong();
}
// Vulnerable: Constant seed for "reproducibility"
public long vulnerableConstantSeed() {
// Vulnerable: Same sequence every time
Random rand = new Random(0xDEADBEEF);
return rand.nextLong();
}
// Vulnerable: Hash of predictable value
public long vulnerableHashSeed(String username) {
// Vulnerable: Attacker knows or can guess username
Random rand = new Random(username.hashCode());
return rand.nextLong();
}
// Vulnerable: Combined weak sources
public long vulnerableCombinedSeed() {
// Vulnerable: Combining weak sources doesn't make strong
long seed = System.currentTimeMillis() ^
Runtime.getRuntime().freeMemory() ^
Thread.currentThread().getId();
Random rand = new Random(seed);
return rand.nextLong();
}
// Vulnerable: Exposed seed
public long vulnerableExposedSeed() {
long seed = System.currentTimeMillis();
System.out.println("Debug: seed=" + seed); // Vulnerable!
return new Random(seed).nextLong();
}
// Vulnerable: Reused seed across instances
private static final long SHARED_SEED = System.currentTimeMillis();
public Random vulnerableSharedSeed() {
// Vulnerable: All instances use same seed
return new Random(SHARED_SEED);
}
}
// Vulnerable: Incorrect PRNG seeding in C
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
// Vulnerable: Time-based seed
void vulnerable_time_seed() {
// Vulnerable: time() returns seconds - very predictable
srand(time(NULL));
}
// Vulnerable: PID-based seed
void vulnerable_pid_seed() {
// Vulnerable: PID has < 16 bits of entropy
srand(getpid());
}
// Vulnerable: Constant seed
void vulnerable_constant_seed() {
// Vulnerable: Same sequence every execution
srand(42);
}
// Vulnerable: Exposed seed
void vulnerable_logged_seed() {
unsigned int seed = time(NULL) ^ getpid();
printf("Using seed: %u\n", seed); // Vulnerable!
srand(seed);
}
// Vulnerable: User input as seed
void vulnerable_user_seed(const char *user_input) {
// Vulnerable: Attacker controls seed
unsigned int seed = 0;
while (*user_input) {
seed = seed * 31 + *user_input++;
}
srand(seed);
}
// Vulnerable: Stack address seed
void vulnerable_address_seed() {
// Vulnerable: ASLR provides limited entropy
int stack_var;
srand((unsigned int)&stack_var);
}
// Vulnerable: Combined weak sources
void vulnerable_combined_seed() {
// Vulnerable: XOR of weak sources is still weak
srand(time(NULL) ^ getpid() ^ getppid());
}
Fixed Code (Python/Java)
# Fixed: Proper PRNG seeding
import secrets
import os
# Fixed: Using secrets module (no manual seeding needed)
def secure_token():
# Fixed: secrets uses system CSPRNG internally
return secrets.token_hex(32)
# Fixed: Proper seeding if custom PRNG needed
def secure_seeded_random():
import random
# Fixed: Seed with sufficient entropy from OS
seed_bytes = os.urandom(32) # 256 bits
random.seed(seed_bytes)
return random
# Fixed: Keep seed secret
def secure_random_with_audit():
# Fixed: Don't log the actual seed
import hashlib
seed = os.urandom(32)
# Log only a non-reversible identifier
seed_id = hashlib.sha256(seed).hexdigest()[:16]
print(f"Initialized random generator (id: {seed_id})")
import random
random.seed(seed)
return random
# Fixed: Independent seeds for multiple instances
class SecureRandomPool:
def __init__(self, count):
self.generators = []
for _ in range(count):
# Fixed: Each instance gets independent seed
import random
gen = random.Random()
gen.seed(os.urandom(32))
self.generators.append(gen)
# Fixed: Periodic reseeding
class ReseedingRandom:
def __init__(self, reseed_interval=1000):
import random
self._random = random.Random()
self._reseed()
self._count = 0
self._reseed_interval = reseed_interval
def _reseed(self):
# Fixed: Reseed from system entropy
self._random.seed(os.urandom(32))
self._count = 0
def getrandbits(self, bits):
self._count += 1
if self._count >= self._reseed_interval:
self._reseed()
return self._random.getrandbits(bits)
// Fixed: Proper PRNG seeding in Java
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public class SecureSeeding {
// Fixed: Use SecureRandom which seeds itself properly
public byte[] secureRandomBytes(int length) throws NoSuchAlgorithmException {
// Fixed: SecureRandom handles seeding internally
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] bytes = new byte[length];
sr.nextBytes(bytes);
return bytes;
}
// Fixed: Proper seeding if needed
public SecureRandom secureSeededRandom() throws NoSuchAlgorithmException {
SecureRandom sr = new SecureRandom();
// Fixed: generateSeed gets entropy from system
byte[] seed = sr.generateSeed(32); // 256 bits
sr.setSeed(seed);
return sr;
}
// Fixed: Independent seeds for multiple instances
public SecureRandom[] createRandomPool(int count)
throws NoSuchAlgorithmException {
SecureRandom[] pool = new SecureRandom[count];
for (int i = 0; i < count; i++) {
// Fixed: Each instance independently seeded
pool[i] = SecureRandom.getInstanceStrong();
}
return pool;
}
// Fixed: Secure audit logging
public SecureRandom secureWithAudit() throws NoSuchAlgorithmException {
SecureRandom sr = SecureRandom.getInstanceStrong();
// Fixed: Log only non-sensitive info
System.out.println("SecureRandom initialized: " +
sr.getAlgorithm() + " provider: " +
sr.getProvider().getName());
return sr;
}
// Fixed: Periodic reseeding
public class ReseedingSecureRandom {
private SecureRandom secureRandom;
private int callCount = 0;
private final int reseedInterval = 1000;
public ReseedingSecureRandom() throws NoSuchAlgorithmException {
secureRandom = SecureRandom.getInstanceStrong();
}
public synchronized byte[] nextBytes(int length) {
byte[] bytes = new byte[length];
secureRandom.nextBytes(bytes);
callCount++;
if (callCount >= reseedInterval) {
// Fixed: Periodic reseed
secureRandom.setSeed(secureRandom.generateSeed(32));
callCount = 0;
}
return bytes;
}
}
}
// Fixed: Proper PRNG seeding in C
#include <openssl/rand.h>
#include <stdio.h>
// Fixed: Use OpenSSL RAND which handles seeding
int secure_random(unsigned char *buffer, size_t length) {
// Fixed: RAND_bytes uses system entropy
return RAND_bytes(buffer, length) == 1 ? 0 : -1;
}
// Fixed: Seed from /dev/urandom if manual seeding needed
int secure_seed_prng() {
unsigned char seed[32]; // 256 bits
// Fixed: Read from system entropy source
FILE *f = fopen("/dev/urandom", "rb");
if (!f) return -1;
if (fread(seed, 1, sizeof(seed), f) != sizeof(seed)) {
fclose(f);
return -1;
}
fclose(f);
// Fixed: Add entropy to OpenSSL
RAND_seed(seed, sizeof(seed));
// Fixed: Clear seed from memory
OPENSSL_cleanse(seed, sizeof(seed));
return 0;
}
// Fixed: Audit without exposing seed
int secure_init_with_audit() {
if (secure_seed_prng() != 0) {
fprintf(stderr, "Failed to seed PRNG\n");
return -1;
}
// Fixed: Only log status, not actual seed
if (RAND_status() == 1) {
printf("PRNG properly seeded\n");
}
return 0;
}
// Fixed: Using getrandom() on Linux
#ifdef __linux__
#include <sys/random.h>
int secure_random_linux(unsigned char *buffer, size_t length) {
// Fixed: getrandom() blocks until entropy available
ssize_t result = getrandom(buffer, length, 0);
return result == (ssize_t)length ? 0 : -1;
}
#endif
The fix uses proper entropy sources for seeds, keeps seeds secret, and implements periodic reseeding.
Exploited in the Wild
Debian OpenSSL Vulnerability (CVE-2008-0166)
Debian's OpenSSL package incorrectly seeded the PRNG using only the process ID, generating only 65,536 unique key pairs across all affected systems.
Cloud Application Time-Based Seeding (CVE-2020-7010)
A Kubernetes cloud application generated passwords using a weak RNG seeded with deployment time, allowing attackers to predict generated credentials.
Tools to Test/Exploit
-
untwister — Recovers PRNG state and seed from observed outputs.
-
randcrack — Cracks Python's random module given sufficient outputs.
-
NIST Statistical Test Suite — Tests randomness quality.
CVE Examples
-
CVE-2008-0166 — Debian OpenSSL PID-only seeding.
-
CVE-2016-10180 — Router PIN generation using rand(time(0)).
-
CVE-2018-12520 — Unseeded PRNG for session ID generation.
References
-
MITRE Corporation. "CWE-335: Incorrect Usage of Seeds in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/335.html
-
NIST. "Recommendation for Random Number Generation." SP 800-90A. 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.