Every key, token, nonce, session ID and API key is only as strong as the random numbers that produced it, and weak randomness fails silently. A predictable generator does not crash or log an error: it produces values that look random but can be guessed, sometimes years later, by anyone who works out how they were made. Randomness in security means unpredictability to an attacker, which is a stricter property than "looks statistically random". This guide explains entropy, the difference between ordinary and cryptographically secure generators, the NIST SP 800-90 series, the operating system and language interfaces to use, how long tokens should be, and the failures that taught the industry these lessons. It is written for developers, security engineers and reviewers who generate or audit secrets.
Key takeaways
- Use the operating system CSPRNG through a high-level API (
secrets,crypto.randomBytes,SecureRandom,crypto/rand,RandomNumberGenerator) and never a general-purpose PRNG such asMath.random(),rand()ormt_rand()for anything security relevant. - Entropy is the scarce input, not the output: a well-seeded CSPRNG can produce unlimited secure output, but no amount of mixing can create entropy that was never collected.
- On Linux, prefer getrandom() or /dev/urandom; the man pages advise against
/dev/randomandGRND_RANDOMexcept for long-term key generation. - Size secrets by bits of entropy, not characters: at least 128 bits for session IDs and tokens, 256 bits for long-lived API keys and secrets, encoded however you like.
- The riskiest moment is first boot: embedded devices, cloned virtual machines and fresh containers that generate keys before the kernel pool is initialised have produced most of the large-scale real-world failures.
What randomness means in security
In plain terms, a random value is secure if an attacker who knows everything about the system except the secret seed still cannot predict it better than by guessing. Statistical tests can show that output is unbiased, but RFC 4086 is explicit that passing statistical tests does not prove unpredictability. A Mersenne Twister passes many statistical tests and is still fully predictable once an attacker has observed enough of its output.
- Entropy: a measure of unpredictability, counted in bits. A value with 128 bits of entropy has 2128 equally likely possibilities. A 32-character hex string generated from a 32-bit seed still has only 32 bits of entropy.
- Entropy source (noise source): a physical or system process that is genuinely unpredictable, such as thermal noise, ring oscillator jitter or interrupt timing. Output is usually biased and must be conditioned. NIST SP 800-90B covers how such sources are designed and validated.
- PRNG: a deterministic algorithm that expands a seed into a long sequence. Fast and repeatable, which suits simulations, games and testing, but its internal state can often be recovered from outputs.
- CSPRNG or DRBG: a deterministic generator built from a cryptographic primitive (hash, HMAC, block cipher or stream cipher) so that output cannot be predicted and past output cannot be reconstructed even if some output is seen. NIST calls these Deterministic Random Bit Generators (DRBGs) in SP 800-90A.
- RBG: NIST's term for the whole construction: an entropy source feeding a DRBG, defined in SP 800-90C.
| Generator type | Examples | Predictable from output? | Use for |
|---|---|---|---|
| Non-cryptographic PRNG | Mersenne Twister (Python random, PHP mt_rand()), Java java.util.Random, C rand(), JavaScript Math.random() | Yes, often after a modest number of outputs | Simulation, sampling, games, test data only |
| CSPRNG / DRBG | Hash_DRBG, HMAC_DRBG, CTR_DRBG (SP 800-90A), ChaCha20-based kernel generators | No, if correctly seeded | Keys, nonces, IVs, tokens, salts, session IDs, passwords |
| Hardware entropy source | CPU instructions, TPM RNG, HSM noise sources | No, but may be biased or fail | Seeding and reseeding a DRBG, rarely used raw |
| Full RBG | OS kernel generator, FIPS-validated module RNG | No | The interface applications should call |
The NIST SP 800-90 series and entropy sources
The NIST series is the reference model most validated cryptographic modules follow, and it splits the problem into three documents.
| Document | Status | What it covers |
|---|---|---|
| SP 800-90A Rev. 1 | Final, June 2015 (a Rev. 2 draft is listed by NIST) | DRBG mechanisms based on hash functions or block ciphers: Hash_DRBG, HMAC_DRBG and CTR_DRBG. Rev. 1 is the version from which Dual_EC_DRBG was removed. |
| SP 800-90B | Final, January 2018 (NIST noted errata to fix in a future update) | Design principles and requirements for entropy sources, health tests and the validation tests used to estimate min-entropy. |
| SP 800-90C | Final, 25 September 2025 | How to combine 90A DRBGs with 90B entropy sources into complete RBGs, with four constructions: RBG1, RBG2, RBG3 and RBGC. |
RFC 4086 (BCP 106) gives the engineering view in plainer language: collect from several uncorrelated sources, mix them with a strong cryptographic function, and remember that mixing preserves entropy but never adds it. It singles out seeding from the system clock as a serious weakness, because an attacker who knows roughly when a generator started only has to try a small set of seeds. It also notes that a few hundred bits of real entropy is enough to seed a strong generator that then produces everything else.
- Health tests matter: an entropy source that silently fails (a stuck hardware bit, a virtualised device returning zeros) must be detected. SP 800-90B requires continuous health tests for this reason.
- Do not trust a single source: modern kernels mix CPU instructions, interrupt timing, device data and hypervisor-supplied seed material, so the failure or subversion of one input does not leave the pool empty.
Operating system interfaces
Applications should almost never implement their own generator. The kernel or platform cryptography library already runs a seeded, reseeding CSPRNG, and every major language wraps it.
| Platform | Interface | Behaviour to know |
|---|---|---|
| Linux (3.17+) | getrandom(buf, len, 0) | Uses the urandom source; blocks until the pool is initialised at boot, then never blocks. Requests up to 256 bytes always return in full once initialised. With GRND_NONBLOCK it fails with EAGAIN instead of blocking. Always check the return value. |
| Linux | /dev/urandom | Never blocks. Before the pool is initialised it can return output the man page describes as possibly low entropy and unsuitable for cryptography. Prefer getrandom() in new code. |
| Linux | /dev/random, getrandom(..., GRND_RANDOM) | The man page advises avoiding these except for long-term key generation: they can block and add partial-read complexity without a meaningful security gain. |
| Windows | BCryptGenRandom(NULL, buf, len, BCRYPT_USE_SYSTEM_PREFERRED_RNG) | CNG API; the default provider implements SP 800-90 CTR_DRBG. With the system-preferred flag the algorithm handle must be NULL. The BCRYPT_RNG_USE_ENTROPY_IN_BUFFER flag is ignored from Windows 8. |
| macOS, iOS, OpenBSD | arc4random_buf(), SecRandomCopyBytes | System CSPRNG; the old RC4-based name is historical. |
The Linux man page also gives a useful sense of scale: a 3072-bit RSA key needs only around 16 bytes of seed material, and code that reads more than 256 bits per call, or per reseed interval, suggests the surrounding cryptography is not carefully implemented.
Language APIs: secure and insecure choices
The OWASP Cryptographic Storage Cheat Sheet lists the functions to avoid and to use. This is the table reviewers and static analysis rules should enforce.
| Language | Never for security | Use |
|---|---|---|
| Python | random module | secrets.token_bytes(), token_hex(), token_urlsafe(), secrets.choice(), secrets.SystemRandom |
| Node.js | Math.random() | crypto.randomBytes(), crypto.randomInt(), crypto.randomUUID() |
| Java | Math.random(), java.util.Random, SplittableRandom, ThreadLocalRandom | java.security.SecureRandom, UUID.randomUUID() |
| .NET / C# | System.Random | RandomNumberGenerator |
| Go | math/rand | crypto/rand (rand.Read, rand.Text from Go 1.24) |
| PHP | rand(), mt_rand(), lcg_value(), uniqid(), array_rand() | random_bytes(), random_int(), Random\Engine\Secure (8.2) |
| Ruby | rand(), Random | SecureRandom |
| C / C++ | rand(), random() | getrandom(2), or the platform crypto library |
Details that catch experienced developers
- Java setSeed: the SecureRandom documentation says calling
setSeedbefore the firstnextBytesstops the generator seeding itself, so the caller's seed becomes the only entropy. A fixed or time-based seed there turns SecureRandom into a predictable generator. UsegetInstanceStrong()only for high-value long-lived keys; it reads thesecurerandom.strongAlgorithmsproperty and may block. - Python token length: the
secretsdefault size (DEFAULT_ENTROPY) is documented as subject to change, and the docs say 32 bytes (256 bits) was considered sufficient as of 2015. Pass an explicit byte count so token length is a deliberate, reviewable choice. - Go:
crypto/randusesgetrandom(2)on Linux,arc4random_bufon Apple platforms andProcessPrngon Windows; the documentation statesReadnever returns an error andTextreturns at least 128 bits of randomness. - UUIDs are not tokens by default: OWASP notes version 1 UUIDs combine a timestamp and MAC address and are not random, and version 4 randomness depends on the implementation. Use a CSPRNG token where unpredictability matters.
- Modulo bias:
random_bytes() % nskews results when n is not a power of two. Use the library's bounded function (secrets.randbelow,crypto.randomInt,random_int) to pick PINs, codes or characters.
How much randomness: tokens, keys and identifiers
Length guidance is about entropy, not string length. Encoding changes the number of characters (hex uses 2 per byte, base64url about 1.3) but not the strength.
| Value | Minimum entropy | Basis | Example |
|---|---|---|---|
| Session ID | 64 bits minimum, 128 bits recommended | OWASP Session Management Cheat Sheet: at least 64 bits; a CSPRNG producing at least 128 bits if you build your own | secrets.token_urlsafe(16) |
| Server-side stored token (reset link, refresh token, opaque access token) | 128 bits, 160 or more preferred | OWASP Session Management Cheat Sheet | crypto.randomBytes(32) |
| Long-lived API key or client secret | 256 bits | Python secrets documentation (32 bytes) and the long exposure window of non-human credentials | 32 random bytes plus a recognisable prefix |
| Symmetric key (AES-256), HMAC key | Full key length | Key strength equals entropy | Generate inside the KMS or HSM |
| IV or nonce | Per algorithm: unique, and random where the mode requires it | Mode specification (for GCM, uniqueness under a key is critical) | Library-generated, never reused |
| Salt for password hashing | 128 bits is common | Uniqueness per password | Generated by the hashing library |
A recognisable prefix (for example a vendor or environment tag) is not entropy, and neither is a checksum suffix. They help secret scanners find leaked keys, which is useful, but the random portion alone must meet the target. See the API Key Management Guide and the Token and Session Security Guide.
Boot time, virtual machines and containers
The large real-world randomness failures cluster around the moment a system first starts. The "Mining your Ps and Qs" study by Heninger, Durumeric, Wustrow and Halderman found 5.57% of TLS hosts and 9.60% of SSH hosts sharing public keys in a vulnerable way, and recovered RSA private keys for 0.50% of TLS hosts because their keys shared prime factors. Almost all vulnerable hosts were headless embedded devices such as routers, firewalls and management cards that generated keys on first boot with few sources of randomness, across 54 manufacturers.
- Do not generate keys at image build time: SSH host keys, TLS keys and machine identities baked into a golden image or container image are shared by every instance. Generate them on first boot, after the RNG is initialised, or issue them from a central PKI.
- Cloned and restored VMs: snapshotting or cloning a running VM can duplicate the guest's generator state, so two clones may produce the same "random" values until they reseed. Prefer fresh boots from images over cloning running machines, and give guests a hypervisor entropy device (virtio-rng or the cloud provider's equivalent).
- Containers share the host kernel RNG: that is good news for entropy, but user-space generators seeded once at process start (a forked worker pool, for example) can repeat state across forks unless the library reseeds after
fork(). - Use blocking semantics at boot:
getrandom()withoutGRND_NONBLOCKwaits until the pool is ready, which is exactly what first-boot key generation needs. Code that reads/dev/urandomearly in boot does not get that guarantee.
Choosing the right source
- OS CSPRNG vs a library DRBG: the OS interface is the default for almost all applications. A separate, validated DRBG is justified when a FIPS 140-3 boundary requires it; Go, for example, routes output through an SP 800-90A Rev. 1 DRBG in FIPS 140-3 mode.
- OS CSPRNG vs HSM or KMS: generate long-lived, high-value keys (CA keys, signing keys, KMS root keys) inside the HSM or cloud KMS so the key never exists outside the boundary. Tokens and session IDs belong in the application using the OS generator.
- Hardware RNG on a device: TPM or secure element output is a good input to the kernel pool, but ROCA showed hardware key generation can itself be flawed. Prefer designs where keys can be regenerated and rotated.
- Small vs large organisations: a small team gets most of the value from a lint rule banning insecure APIs and a short standard for token lengths. A large organisation also needs an inventory of who generates keys where (devices, images, CI), FIPS module requirements and a tested key regeneration plan. See the Cryptographic Inventory and CBOM Guide.
Implementing and testing a randomness standard
- Write the standard: one page listing approved APIs per language (the table above), minimum entropy per value type, and the rule that keys are generated in KMS or HSM where available.
- Find existing use: search code for insecure calls (
Math.random,java.util.Random,mt_rand,rand(,import random,math/rand,System.Random) and triage each by context: flag only those producing secrets, user-facing IDs or security decisions. - Enable static analysis rules in warn mode: most SAST tools ship rules mapped to CWE-338 and CWE-330. Run them in report-only mode for a sprint to measure noise, then block on new findings in pull requests.
- Fix and rotate: replacing the generator does not fix values already issued. Invalidate sessions, reissue tokens and API keys, and regenerate keys produced by the weak code. Debian's DSA-1571 made the same point: upgrading the package was not enough, all affected key material had to be regenerated.
- Check build and boot pipelines: confirm no keys are generated during image builds, and that first-boot key generation waits for the RNG.
- Test: unit tests should assert token length and character set, and a review should confirm that tests mocking the RNG do not leak into production configuration.
- Roll back safely: enforcement is a CI rule, so rollback means switching it back to warn mode; never roll back by restoring an insecure generator.
Detection and measurement
- Duplicate key detection: scan certificate inventories and SSH host key collections for identical public keys or shared RSA factors across hosts; duplicates point to image-baked keys or boot entropy problems.
- Token collisions: any collision of a supposedly random token or ID in production logs is a strong signal of a broken or poorly seeded generator.
Metrics that show the programme is working:
- Number of open SAST findings for CWE-338 and CWE-330 in security-relevant code, trending to zero.
- Percentage of repositories with the insecure-RNG rule enforced in CI.
- Count of duplicate public keys found across the estate per quarter.
- Percentage of long-lived API keys and client secrets meeting the 256-bit standard (from the issuing system's metadata).
- Percentage of high-value keys generated inside a KMS or HSM rather than on application hosts.
Where identity fits
Non-human identities are where randomness matters most and is checked least. API keys, client secrets, webhook signing secrets, service account passwords and bootstrap tokens are generated by scripts, CI jobs and vendor platforms, often once, and then live for years. A key generated with a weak PRNG keeps its weakness for the whole of that life. Generate machine credentials through the secrets manager or platform, record which generator and length were used, and prefer short-lived credentials so a flawed batch can be replaced quickly. See the Secrets Management Guide.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | SC-12 Cryptographic Key Establishment and Management | Key generation, including the quality of random input |
| NIST SP 800-53 Rev. 5 | SC-13 Cryptographic Protection | Use of approved cryptography, including approved RBGs |
| NIST CSF 2.0 | PR.DS-01 | Protection of data at rest depends on keys generated with sufficient entropy |
| MITRE CWE | CWE-330 Use of Insufficiently Random Values; CWE-338 Use of Cryptographically Weak PRNG | Weakness IDs used by SAST tools and CVE records |
| MITRE CAPEC | CAPEC-59 Session Credential Falsification through Prediction | Attack pattern exploiting predictable session tokens (related to CWE-330 and CWE-331) |
| MITRE CAPEC | CAPEC-485 Signature Spoofing by Key Recreation | Recreating private keys from weak generation, as with Debian 2008 and ROCA |
| MITRE CAPEC | CAPEC-112 Brute Force | Low-entropy secrets shrink the search space |
Common failure patterns
- Removing the entropy by accident: a Debian-specific change to OpenSSL, shipped from version 0.9.8c-1 in September 2006 and disclosed in DSA-1571 (CVE-2008-0166) on 13 May 2008, made the generator predictable. SSH, OpenVPN, DNSSEC and X.509 keys became guessable, and CWE lists it as producing only 65,536 unique keys. Every DSA key used on an affected system had to be treated as compromised, because DSA signatures leak the key when the per-signature random value is weak.
- Trusting a generator that cannot be audited: after reports in September 2013, NIST removed Dual_EC_DRBG from its draft SP 800-90A Rev. 1 in April 2014, citing a lack of public confidence, and told users to move to the remaining three DRBGs and to ask vendors whether their modules used it. The lesson is to prefer simple, well-analysed constructions and to keep the ability to change them. See the Crypto-Agility Guide.
- Flawed key generation in hardware: ROCA (CVE-2017-15361) was a flaw in an Infineon RSA library used in TPM firmware and other devices that produced RSA keys weak enough to make factoring practical; affected uses included BitLocker with TPM 1.2 and YubiKey 4 PGP key generation before 4.3.5. Hardware-generated keys still need an inventory and a regeneration path.
- Keys generated at first boot with no entropy: the Ps and Qs research showed thousands of embedded devices with factorable or shared keys. Image-baked keys and cloned VMs repeat the pattern in cloud estates.
- General-purpose PRNGs for tokens: CWE-338 cites a PHP framework using
mt_rand()for tokens (CVE-2021-3692). Password reset tokens, invite codes and API keys built on non-cryptographic generators can be predicted from a few observed values. - Treating identifiers as secrets: version 1 UUIDs, timestamps, database sequence IDs and hashes of predictable inputs are unique but not unpredictable, and should never act as bearer credentials.
Practitioner checklist
- Publish an approved-API list per language and ban
Math.random,java.util.Random,mt_rand,rand()and Pythonrandomfor security use. - Enforce the ban with SAST rules for CWE-338 and CWE-330, warn first, then block new findings.
- Set minimum entropy: 128 bits for session IDs and tokens, 256 bits for long-lived API keys and secrets, full length for keys.
- Use
getrandom()(not/dev/random) on Linux andBCryptGenRandomwith the system-preferred RNG on Windows in native code. - Never call
SecureRandom.setSeedwith a fixed or time-based value before first use. - Generate high-value keys inside a KMS or HSM; never at image build time.
- Give virtual machines a hypervisor entropy device and avoid cloning running VMs that hold key material.
- Scan certificate and SSH key inventories for duplicate public keys every quarter.
- When a weak generator is found, rotate every secret it produced, not only the code.
- Use bounded random functions to avoid modulo bias in codes and PINs.
Standards and references
- NIST: SP 800-90A Rev. 1, Recommendation for Random Number Generation Using Deterministic Random Bit Generators
- NIST: SP 800-90B, Recommendation for the Entropy Sources Used for Random Bit Generation
- NIST: SP 800-90C, Recommendation for Random Bit Generator (RBG) Constructions
- IETF: RFC 4086, Randomness Requirements for Security
- Linux man-pages: random(7), Overview of interfaces for obtaining randomness
- OWASP: Cryptographic Storage Cheat Sheet
- Debian: DSA-1571-1 openssl, predictable random number generator
- Heninger, Durumeric, Wustrow and Halderman: Mining Your Ps and Qs, Detection of Widespread Weak Keys in Network Devices
Related NHI Mgmt Group resources: Cryptography Basics Guide · API Key Management Guide · Secrets Management Guide · Cryptographic Key Management Guide · Hashing Guide