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.Randomfor 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
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
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.