Even with an unbroken algorithm, a public-key system collapses if it draws its keys from a weak random source. What separates a "normal" random generator from a cryptographically secure one?
Everything rests on the private key — and every session key and nonce — being unpredictable. A "normal" PRNG only promises numbers that look statistically uniform; it is fully deterministic, so an attacker who sees a few outputs can reproduce all the others. A cryptographically secure RNG (CSPRNG) adds the missing requirement: even given past outputs, the next one must be infeasible to predict.
* An LCG (java.util.Random) is a deterministic recurrence: observe two outputs, recover the state, predict every value. A CSPRNG removes exactly this predictability. *
A classic "normal" generator is a linear congruential generator (LCG) — the math behind java.util.Random:
RN₁ = seed
RNₙ₊₁ = (A · RNₙ + C) mod M
Fast and evenly distributed — perfectly fine for a game or a simulation. But the recurrence is public: recover the internal state from two consecutive outputs and every future (and past) value falls out. If that same generator picked your RSA primes or a Diffie-Hellman private value, the "secret" key is trivially recomputable — the algorithm was never touched, yet the system is wide open. For anything cryptographic use java.security.SecureRandom, or the OS CSPRNG (/dev/urandom, getrandom()).
This is one instance of the broader "weak cryptographic functions" pitfall (OWASP flags it under broken authentication / cryptographic failures): using primitives that are broken or not cryptographically strong, or keys that are simply too short. The defenses are all about not rolling your own:
- Use only vetted, cryptographically strong functions — and verify every library you depend on does too.
- Build systems so a primitive can be swapped easily when one is later broken (crypto-agility).
- Never implement cryptographic functions yourself — a predictable RNG or a timing leak destroys security while the code still "works".
Tip: The tell-tale smell is Math.random() / rand() / java.util.Random anywhere near a key, token, password-reset link, or IV. These are predictability bugs, not correctness bugs — the output looks random in testing and is fully exposed in production.
Go deeper:
Cryptographically secure PRNG (Wikipedia) — why ordinary PRNGs fail and CSPRNGs resist prediction.
Linear congruential generator (Wikipedia) — the exact recurrence and its explicit "must not be used for cryptography" warning.