Math.random() is not cryptographically secure. Its output is deterministic given the internal state, which means passwords generated with it are guessable in principle. Any password or token generator must use the Web Crypto API instead: crypto.getRandomValues().
The correct core
function securePassword(length = 20) {
const charset =
'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*';
const bytes = new Uint8Array(length * 2);
crypto.getRandomValues(bytes); // CSPRNG, provided by the OS
const limit = 256 - (256 % charset.length);
const out = [];
for (const b of bytes) {
// Rejection sampling: skip bytes that would cause modulo bias
if (b < limit && out.length < length) out.push(charset[b % charset.length]);
}
return out.join('');
}
Two details worth understanding:
-
crypto.getRandomValues()pulls from the operating system's cryptographically secure random number generator — the same source TLS uses. It cannot be seeded or predicted from JavaScript. -
Rejection sampling fixes modulo bias. If your charset length doesn't divide 256 evenly, a naive
byte % charset.lengthmakes some characters slightly more likely. Discarding out-of-range bytes keeps the distribution uniform. (In production, loop and refill until the password is full rather than hoping one buffer is enough.)
How strong is it?
With a 70-character set and 20 characters, you get log2(70^20) ≈ 122 bits of entropy. That's beyond brute force for any realistic adversary — length plus a real CSPRNG is the entire game.
The checklist
- Never
Math.random()for secrets. Ever. - Generate locally — a password that never touches a network can't be intercepted in transit or logged by a server.
- Don't get clever with "memorable" patterns that shrink the search space.
Want one without writing it yourself? The Password Generator on ToolVero uses exactly this approach — everything runs locally in your tab.
Top comments (0)