Same Seed in Pseudo-Random Number Generator (PRNG)
Description
Same Seed in Pseudo-Random Number Generator (PRNG) is a vulnerability that occurs when a PRNG is initialized with the same seed value across multiple instances, executions, or systems. Since PRNGs are deterministic algorithms, identical seeds produce identical sequences of output values. When the same seed is reused, attackers who can determine the seed value can predict all subsequent random numbers generated. This vulnerability commonly manifests as hardcoded seeds in source code, seeds that reset on application restart, shared seeds across distributed systems, or seeds derived from constant values during initialization.
Risk
Reusing PRNG seeds creates severe and often easily exploitable vulnerabilities. When all instances of an application use the same seed, every instance produces the same "random" sequence. An attacker who obtains or guesses the seed from one instance can predict values across all instances. This enables mass exploitation where compromising one system's randomness compromises all systems. In scenarios like cryptocurrency wallet generation, same seeds have led to theft of funds when multiple wallets generated identical keys. Session tokens become predictable across all users, authentication challenges can be pre-computed, and encryption provides no protection when keys are identical. The vulnerability is particularly dangerous because it may not be detected through normal testing - values appear random in isolation.
Solution
Never reuse PRNG seeds across instances or executions. Generate unique seeds from cryptographically secure entropy sources for each PRNG instance. Use operating system-provided CSPRNGs that handle seeding automatically and correctly. Implement seed management that ensures freshness: generate new seeds at startup from /dev/urandom or equivalent, never persist seeds to storage, and implement periodic reseeding. Avoid hardcoding seeds in source code even for testing purposes - use configuration or environment variables for test seeds and ensure they cannot reach production. For distributed systems, ensure each node generates its own independent seed rather than sharing a common seed.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control When multiple systems use the same PRNG seed, an attacker who predicts the sequence from one system can bypass authentication on all systems using that seed. |
| Confidentiality | Scope: Confidentiality Identical seeds produce identical encryption keys, nonces, and IVs, allowing attackers to decrypt data across all affected systems once they determine the seed. |
| Integrity | Scope: Integrity Security tokens, CSRF protections, and integrity checks become predictable and forgeable when based on same-seeded PRNGs. |
Example Code
Vulnerable Code (Python/Java)
The following examples demonstrate same seed vulnerabilities:
# Vulnerable: Same seed reused
import random
# Vulnerable: Hardcoded seed in code
def vulnerable_hardcoded_seed():
random.seed(12345) # Same every time!
return random.randint(0, 2**64)
# Vulnerable: Class with constant seed
class VulnerableTokenGenerator:
def __init__(self):
# Vulnerable: All instances use same seed
random.seed(0xDEADBEEF)
def generate_token(self):
return format(random.getrandbits(64), '016x')
# Vulnerable: Shared seed across module
GLOBAL_SEED = 42
random.seed(GLOBAL_SEED)
def vulnerable_session_id():
# Vulnerable: Uses globally seeded random
return random.getrandbits(128)
# Vulnerable: Seed from constant config
CONFIG_SEED = "production_seed_value"
def vulnerable_config_seed():
random.seed(hash(CONFIG_SEED))
return random.random()
# Vulnerable: Same seed on restart
class VulnerablePersistentRandom:
SEED = 999
def __init__(self):
# Vulnerable: Same seed every restart
random.seed(self.SEED)
def next(self):
return random.random()
# Vulnerable: SDK with fixed seed (real CVE pattern)
class VulnerableSDK:
_SEED = b"static_seed_value"
def __init__(self):
# Vulnerable: All SDK users get same random!
import hashlib
seed = int.from_bytes(hashlib.sha256(self._SEED).digest()[:8], 'big')
random.seed(seed)
def generate_key(self):
return bytes([random.randint(0, 255) for _ in range(32)])
// Vulnerable: Same seed reused in Java
import java.util.Random;
public class VulnerableSameSeed {
// Vulnerable: Hardcoded seed constant
private static final long SEED = 12345L;
// Vulnerable: All instances share same seed
public String vulnerableToken() {
Random rand = new Random(SEED);
return Long.toHexString(rand.nextLong());
}
// Vulnerable: Static shared Random with fixed seed
private static final Random sharedRandom = new Random(0xCAFEBABE);
public static long vulnerableShared() {
return sharedRandom.nextLong();
}
// Vulnerable: Seed from constant string
public Random vulnerableStringSeed() {
String seedString = "fixed_seed_string";
return new Random(seedString.hashCode());
}
// Vulnerable: Same seed derived from class name
public Random vulnerableClassSeed() {
return new Random(this.getClass().getName().hashCode());
}
// Vulnerable: Environment variable with default
public Random vulnerableEnvSeed() {
String seedStr = System.getenv("RANDOM_SEED");
if (seedStr == null) {
seedStr = "default_seed"; // Vulnerable: Everyone uses default!
}
return new Random(seedStr.hashCode());
}
// Vulnerable: Seed file shared across instances
public Random vulnerableFileSeed() throws Exception {
// If all instances read same file, all get same seed
byte[] seedBytes = java.nio.file.Files.readAllBytes(
java.nio.file.Paths.get("/etc/app/random.seed"));
long seed = java.nio.ByteBuffer.wrap(seedBytes).getLong();
return new Random(seed);
}
}
// Vulnerable: Same seed reused in C
#include <stdlib.h>
#include <string.h>
// Vulnerable: Hardcoded seed
#define RANDOM_SEED 12345
void vulnerable_hardcoded_init() {
srand(RANDOM_SEED); // Same every execution!
}
// Vulnerable: Seed from compile-time constant
void vulnerable_compile_time_seed() {
srand(__LINE__ * __COUNTER__); // Same in every build!
}
// Vulnerable: Same seed from string constant
void vulnerable_string_seed() {
const char *seed_str = "application_random_seed";
unsigned int seed = 0;
while (*seed_str) {
seed = seed * 31 + *seed_str++;
}
srand(seed); // Same every time!
}
// Vulnerable: Seed reset on function call
void vulnerable_reset_seed() {
// Vulnerable: Reseeds with same value each call
static const unsigned int FIXED_SEED = 999;
srand(FIXED_SEED);
}
// Vulnerable: Shared seed in library
static int initialized = 0;
static const unsigned int LIB_SEED = 0xABCD1234;
void vulnerable_library_random() {
if (!initialized) {
srand(LIB_SEED); // All library users get same sequence
initialized = 1;
}
}
// Vulnerable: Seed from static configuration
typedef struct {
unsigned int random_seed;
} Config;
static const Config DEFAULT_CONFIG = { .random_seed = 54321 };
void vulnerable_config_seed(const Config *config) {
if (config == NULL) {
config = &DEFAULT_CONFIG;
}
srand(config->random_seed); // Often uses default
}
Fixed Code (Python/Java)
# Fixed: Unique seeds for each instance
import os
import secrets
# Fixed: Use secrets module (no manual seeding)
def secure_token():
return secrets.token_hex(32)
# Fixed: Unique seed per instance from system entropy
class SecureTokenGenerator:
def __init__(self):
import random
self._random = random.Random()
# Fixed: Unique seed from OS entropy
self._random.seed(os.urandom(32))
def generate_token(self):
return format(self._random.getrandbits(64), '016x')
# Fixed: Factory that creates independently seeded generators
def create_random_generator():
import random
gen = random.Random()
gen.seed(os.urandom(32)) # Fixed: New entropy each time
return gen
# Fixed: No global seeding - use secrets
def secure_session_id():
return secrets.token_hex(16)
# Fixed: Seed from runtime entropy, not config
def secure_config_random():
# Fixed: Config cannot affect seed
return secrets.token_bytes(32)
# Fixed: New seed on each restart
class SecurePersistentRandom:
def __init__(self):
import random
self._random = random.Random()
# Fixed: Fresh seed from system entropy
self._random.seed(os.urandom(32))
def next(self):
return self._random.random()
# Fixed: SDK with proper random
class SecureSDK:
def __init__(self):
# Fixed: No fixed seed - use system entropy
pass
def generate_key(self):
# Fixed: Use system CSPRNG
return os.urandom(32)
// Fixed: Unique seeds in Java
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public class SecureSeeding {
// Fixed: SecureRandom handles seeding properly
public String secureToken() throws NoSuchAlgorithmException {
// Fixed: Each call uses properly seeded SecureRandom
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] bytes = new byte[16];
sr.nextBytes(bytes);
return bytesToHex(bytes);
}
// Fixed: Instance-specific seeding
public class SecureGenerator {
private final SecureRandom secureRandom;
public SecureGenerator() throws NoSuchAlgorithmException {
// Fixed: Unique seed per instance
this.secureRandom = SecureRandom.getInstanceStrong();
}
public long nextLong() {
return secureRandom.nextLong();
}
}
// Fixed: Factory creates independently seeded instances
public SecureRandom createSecureRandom() throws NoSuchAlgorithmException {
// Fixed: Each instance independently seeded
return SecureRandom.getInstanceStrong();
}
// Fixed: No seed from strings
public SecureRandom noStringSeed() throws NoSuchAlgorithmException {
// Fixed: System entropy, not derived from string
return new SecureRandom();
}
// Fixed: Environment variable only affects non-crypto random
public SecureRandom secureEnvRandom() throws NoSuchAlgorithmException {
// Fixed: Ignore environment for secure random
// Environment might control non-security random for testing
return SecureRandom.getInstanceStrong();
}
// Fixed: No shared seed file
public SecureRandom noFileSeed() throws NoSuchAlgorithmException {
// Fixed: Each instance generates own entropy
SecureRandom sr = new SecureRandom();
sr.nextBytes(new byte[32]); // Force seeding
return sr;
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
// Fixed: Unique seeds in C
#include <openssl/rand.h>
#include <fcntl.h>
#include <unistd.h>
// Fixed: Seed from system entropy
int secure_init_random() {
unsigned char seed[32];
// Fixed: Read from /dev/urandom
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);
// Fixed: Seed OpenSSL
RAND_seed(seed, sizeof(seed));
// Fixed: Clear seed from memory
OPENSSL_cleanse(seed, sizeof(seed));
return 0;
}
// Fixed: Use OpenSSL RAND_bytes
int secure_random_bytes(unsigned char *buffer, size_t length) {
// Fixed: RAND_bytes handles seeding internally
return RAND_bytes(buffer, length) == 1 ? 0 : -1;
}
// Fixed: Per-instance seeding
typedef struct {
unsigned char state[32];
} SecureRandom;
int secure_random_init(SecureRandom *sr) {
// Fixed: Each instance gets unique seed
return secure_random_bytes(sr->state, sizeof(sr->state));
}
// Fixed: Library with proper initialization
static int lib_initialized = 0;
int secure_library_init() {
if (!lib_initialized) {
// Fixed: Seed from system entropy, not constant
if (secure_init_random() != 0) {
return -1;
}
lib_initialized = 1;
}
return 0;
}
// Fixed: No default config seed
typedef struct {
int use_random; // Config doesn't include seed
} SecureConfig;
int secure_config_init(const SecureConfig *config) {
(void)config; // Config doesn't affect random seeding
return secure_init_random(); // Always use system entropy
}
The fix ensures each PRNG instance receives a unique seed from system entropy sources.
Exploited in the Wild
JavaScript SDK Fixed Seed (CVE-2022-39218)
A JavaScript SDK for serverless applications used the same fixed seed for its PRNG, allowing attackers to bypass cryptography by predicting generated values.
Cryptocurrency Wallet Same Keys
Multiple cryptocurrency wallet implementations using same-seeded PRNGs generated identical private keys, enabling theft of funds from all affected wallets.
Tools to Test/Exploit
-
Static Analysis Tools — Can detect hardcoded seeds in source code.
-
untwister — Recovers PRNG state from outputs.
-
randcrack — Exploits predictable PRNG sequences.
CVE Examples
- CVE-2022-39218 — JavaScript SDK fixed seed allowing crypto bypass.
References
-
MITRE Corporation. "CWE-336: Same Seed in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/336.html
-
NIST. "Recommendation for Random Number Generation." SP 800-90A. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final
-
OWASP Foundation. "Insecure Randomness." https://owasp.org/www-community/vulnerabilities/Insecure_Randomness