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

ImpactDetails
Access ControlScope: Access Control

Predictable or exposed seeds allow attackers to compute authentication tokens, session IDs, and access credentials, bypassing authentication and authorization controls.
ConfidentialityScope: Confidentiality

Encryption keys generated from predictable seeds can be reconstructed by attackers, compromising the confidentiality of all data encrypted with those keys.
IntegrityScope: 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


CVE Examples


References

  1. MITRE Corporation. "CWE-335: Incorrect Usage of Seeds in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/335.html

  2. NIST. "Recommendation for Random Number Generation." SP 800-90A. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final

  3. Goldberg, I., Wagner, D. "Randomness and the Netscape Browser." Dr. Dobb's Journal, 1996.