Use window.crypto.getRandomValues for any secret material, not Math.random. Math.random is simple and familiar, but it is a pseudo-random generator seeded and advanced by an algorithm, which makes its outputs predictable enough that an attacker who sees some tokens can infer others. The Web Crypto API method crypto.getRandomValues, by contrast, yields cryptographically strong bytes backed by system entropy sources such as /dev/urandom and is the right choice for passwords, session tokens and nonces. This guide gives the replacement patterns, the arithmetic you need, and the practical checks to deploy crypto.getRandomValues correctly so your secrets stay secret.
1. Why Math.random fails and what crypto.getRandomValues gives you
Math.random is intended for non-cryptographic uses, while Crypto.getRandomValues is the platform API for cryptographically strong randomness.
That contrast matters. Math.random returns a JavaScript floating point value where 0 is included and 1 is excluded. Under the hood it's a pseudo-random number generator, which means its outputs are produced from an internal seed and deterministic algorithm. Because of that design, sequences from Math.random can be predictable enough that an attacker who observes some outputs can infer the seed or future values. For anything used as a secret, that predictability is a security failure.
By contrast, the Web Crypto API documents Crypto.getRandomValues as producing cryptographically strong random values suitable for cryptographic use, subject to implementation details. Implementations typically use a well-seeded pseudo-random number generator backed by platform entropy sources, and user agents are urged to obtain entropy from system-level sources such as /dev/urandom or equivalent. In short, the API is what browsers expose so your code can obtain random bytes that are suitable for passwords, tokens and other secret material.
Worked example: if your code today calls Math.random to pick characters for a password, an attacker who knows the algorithm and can observe outputs might reconstruct the sequence. Replace that call with a crypto-backed read and the underlying entropy comes from the platform, not a guessable seed.
2. How to produce a secure Math.random-style float from getRandomValues
Many code paths expect a uniform floating point number in the range 0 to 1. You don't have to rewrite every consumer to accept raw bytes. The simplest secure pattern is to read a 32-bit unsigned integer from Crypto.getRandomValues and convert it to a float that covers 0 inclusive to 1 exclusive.
The concrete steps are:
First, allocate a typed array such as a Uint32Array of length 1. Second, call Window.crypto.getRandomValues(array). The method overwrites the array in place with secure random bytes. Third, divide the retrieved integer by 0xFFFFFFFF + 1 to produce a floating value f where 0 <= f < 1. Substituting this secure float wherever your code previously used Math.random preserves existing scaling logic while upgrading the entropy source.
Here is a minimal pattern you can adapt.
Function secureRandomFloat() {
const arr = new Uint32Array(1);
window.crypto.getRandomValues(arr);
// 0xFFFFFFFF is the maximum 32-bit unsigned integer.
Return arr[0] / (0xFFFFFFFF + 1);
}
That returned value mirrors the interval semantics of Math.random. Use it wherever your code expects a uniform float in [0,1).
Worked example: if your app scales a Math.random value to an integer range with Math.floor(f * n), replace Math.random with secureRandomFloat and the scaling behaviour remains identical, but the underlying randomness is cryptographically strong.
3. Mapping secure bytes into ranges and alphabets without bias
Getting secure bytes is only half the job.
Mapping those bytes into a small integer range or into an index of a character set requires care. Naive patterns create subtle bias that weakens your generator.
A common mistake is to take a random integer and apply modulo to fit the target range. When the range size doesn't divide the integer space evenly, the remainder operation favours some outputs over others. For password characters and small ranges that bias is material.
There are two safer, commonly recommended approaches.
First, scale a secure float into the desired range. If you produce a secure float f as above, convert it into an integer between min and max with the arithmetic:
Integer = Math.floor(f * (max - min + 1)) + min;
This mirrors the usual Math.random mapping but uses a cryptographic source for f. It avoids modulo bias because the multiplication and floor produce a uniform integer when f is uniform in [0,1).
Second, for character-based passwords it's often cleaner to operate on random bytes directly and use an accept-reject step. The accept-reject method reads a random value that covers a power-of-two space large enough to include your alphabet size, and discards values that would produce uneven mapping.
Worked example: suppose your alphabet has 64 characters. Read a random byte block and convert chunks into integers in the range 0 to 255. For each chunk, if its value is less than 192 then map value % 64 to an index. If the value is 192 or greater, discard it and read another byte. That accept-reject threshold is chosen so the remainder classes are even. The result is an unbiased selection from your 64-character set.
An alternative chunking approach converts multiple bytes into a larger integer and extracts indices for multiple characters at once. Both accept-reject and chunking avoid the bias introduced by a single modulo operation applied to an arbitrary integer.
Documentation and cryptography guidance also emphasise that if you need keys, you should use the Web Crypto API GenerateKey methods rather than rolling your own key material from raw getRandomValues output. GenerateKey is designed to produce keys in formats suitable for the platform and for the algorithm you intend to use.
4. Practical constraints, implementation notes and testing
There are some operational details from the Web Crypto API contract you must respect when you move code away from Math.random.
First, GetRandomValues accepts a set of typed arrays, for example Uint8Array and Uint32Array, and overwrites the contents of the array you pass. Plan memory use accordingly, and remember the method can throw a QuotaExceededError if you request more than 65,536 bytes in a single call. For very large needs, batch your requests instead of trying to read everything at once.
Second, browser behaviour can vary in the exact PRNG algorithm and how much entropy is used under the hood. The specification doesn't mandate a single PRNG algorithm or a specific minimum entropy quantity.
It does require that user agents seed their PRNGs with enough platform entropy and implement efficient, well-defined generators. That means you should code to the API semantics rather than rely on assumptions about the precise algorithm.
Third, GetRandomValues is widely supported in browsers and available from Web Workers. It's also the only member of the Crypto interface that can be used from an insecure context, according to the API documentation. Still, test your replacement in all target browsers and in worker contexts if your code uses them.
Fourth, when converting legacy Math.random consumers, replace calls only where the output is used for secrets. Not every Math.random occurrence needs replacing. The concrete developer action is to find instances that generate passwords, session tokens, authentication nonces or any other secret value, and substitute a crypto-backed generator that reads from getRandomValues.
Fifth, validate the new generator. Check that produced values conform to expected lengths, that distribution appears uniform for your range, and that your implementation handles the 65,536 byte quota without throwing. Run cross-browser tests and include Web Worker paths in your test matrix.
Worked example: replace a function that generated a 32-character password by selecting characters with Math.random with a new routine that reads a bounded number of secure bytes, maps them via accept-reject into the chosen alphabet, and assembles the string. Verify the output length is exactly 32 characters across browsers, and add tests that simulate quota errors by requesting large arrays to confirm your code batches reads correctly.
5. A checklist for migration
Follow these pragmatic steps when upgrading a codebase from Math.random to crypto.getRandomValues.
First, search the code for Math.random uses and identify which ones produce secrets such as passwords, tokens, nonces or cryptographic keys. Second, replace Math.random in those locations with a function that sources bytes from crypto.getRandomValues. You can put in place a secure float pattern using a Uint32Array as shown earlier, or read random bytes directly when mapping to characters. Third, avoid modulo-based mapping that introduces bias; use the secure-float scaling or an accept-reject algorithm. Fourth, for key material prefer the Web Crypto API generateKey methods instead of building keys by hand from raw random bytes. Fifth, add tests for output length, distribution sanity checks and cross-browser behaviour including Worker contexts and the 65,536 byte quota.
Worked example: a small site used Math.random to build session tokens by hashing a string that included Math.random outputs. The migration replaced the Math.random call with a call to secureRandomFloat, kept the rest of the hashing logic intact, and added a test that confirms tokens are the same length and that token generation doesn't throw when the browser is less permissive about entropy sources.
Related Articles
- 3 ways to detect textarea word wrap in JavaScript
- 3 HSE funding routes for course costs and training
- 7 steps to apply for Revenue income tax jobs 2026
One practical rule to keep in your toolbox, stated plainly: avoid allocating huge buffers in a single getRandomValues call, and use the Web Crypto API's key generation routines for key material rather than assembling keys yourself. In short: - Use window.crypto.getRandomValues for any secret material, not Math.random. - To preserve existing code that expects a float, read a 32-bit unsigned integer and divide by 0xFFFFFFFF + 1. - When mapping bytes into a small range or alphabet, use rejection sampling to avoid bias. - Prefer the Web Crypto generateKey and deriveKey methods for cryptographic keys, not manual byte assembly. Sources: MDN Web Docs, entry "Window.crypto.getRandomValues", and the W3C Web Crypto API specification.
This article was created with AI assistance.