Predictable seed for a secure random generator

Avoid initializing a security-sensitive random generator using predictable values alone

Description

Initializing a secure random generator using only the system clock or a fixed value can let an attacker guess the seed and reduce the search space for tokens. SecureRandom behavior depends on its provider; not every implementation produces the same sequence from the same seed.

The Java API states that calling setSeed() before the first nextBytes() can prevent a PRNG-based SecureRandom from self-seeding, leaving the caller responsible for sufficient entropy. By contrast, setSeed() supplements an existing seed rather than replacing it. Adding a timestamp does not by itself mean previously acquired entropy has disappeared.

Potential impact

  • Account or session takeover through guessed tokens or session identifiers
  • Forged requests using predictable CSRF or other security tokens
  • Guessable password-reset or one-time authentication codes
  • A smaller brute-force search space when initialization relies on time

Remediation

  • For ordinary token generation, use new SecureRandom() and rely on the provider's entropy-based initialization. Do not seed it solely with timestamps or constants before its first use.
  • If an explicit seed is necessary, provide enough unpredictable entropy. generateSeed(n) supplies bytes for seeding another generator; separate seeding is usually unnecessary.
  • Do not use java.util.Random for security tokens or session identifiers. Choose sufficient random length, expiration and a reuse policy appropriate to the purpose. Base64 changes representation, not entropy.
  • Generate cryptographic keys with dedicated APIs such as KeyGenerator. When deriving a key from a password, use an appropriate password-based key derivation function.

Examples

Before

java
import java.security.SecureRandom;
import java.util.Base64;

public class InsecureTokenService {
    // Unsafe: initialize from a timestamp
    public String issueToken() {
        SecureRandom rng = new SecureRandom();
        rng.setSeed(System.currentTimeMillis()); // Predictable
        byte[] buf = new byte[16]; // 128 bits
        rng.nextBytes(buf);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
    }
}

After

java
import java.security.SecureRandom;
import java.util.Base64;

public class SecureTokenService {
    private static final SecureRandom RNG = new SecureRandom(); // Self-seed using provider entropy

    public String issueToken() {
        byte[] buf = new byte[32]; // 256 random bits
        RNG.nextBytes(buf);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
    }

    // Optional: obtain a random seed if explicit seeding is required
    public String issueTokenWithExplicitSeed() {
        SecureRandom sr = new SecureRandom();
        byte[] strongSeed = sr.generateSeed(32); // Unpredictable seed
        SecureRandom seeded = new SecureRandom(strongSeed);
        byte[] buf = new byte[32];
        seeded.nextBytes(buf);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
    }
}

The first example seeds the generator with a guessable timestamp before producing bytes. The second uses provider self-seeding in its main path. Its optional method illustrates acquiring a separate random seed; that extra step is not required for ordinary token generation.

References