Back to blog

Password Engineering

Where Password Randomness Comes From

May 12, 2026 · 6 min read

Colored stones scattered in a random-looking pattern
Image by Sara from Pixabay

Password generators look simple from the outside: click a button, get a secret. The important question is what happens before that password appears on screen. Where does the randomness come from? How is it turned into characters or words? And what can a user do if they want to add their own randomness into the mix?

This article explains the source of randomness used by browser-based password generators, how entropy should be understood, and how an optional user interaction, such as a small keyboard-driven game, can be mixed with the browser's cryptographic random source.

The Short Answer

On passwords.lu, password generation happens in the browser. The page asks for cryptographically strong random bytes, turns them into characters or words, and shows the result locally.

That gives us two separate security properties: randomness protects against guessing, and local generation avoids sending the secret through application infrastructure.

Why Local Generation Is Different

Older password-generator websites often generated the secret on the server and then presented it to the user. That model can be implemented honestly, but it creates an avoidable trust problem.

The user has to trust that the server did not log the password, cache it, expose it through analytics, or leak it through infrastructure. Browser-native generation changes that architecture: the server delivers the application code, while the secret itself is created on the user's device.

For a password generator, this is the cleaner privacy model. The fewer systems that ever see the secret, the fewer places there are for it to be mishandled.

Why Randomness Matters

Humans are bad random number generators. Even when we try to be unpredictable, we tend to choose dates, names, keyboard walks, favorite words, memorable shapes, and small variations of things we already know.

Attackers know this. Password-cracking tools do not simply try every possible string from aaa to zzz. They try leaked passwords, dictionaries, common substitutions, keyboard patterns, names, years, and combinations that match human habits.

A secure generator should not ask a person to be creative. It should draw unpredictable values from a cryptographically secure random source, then map those values into characters or words without introducing avoidable bias.

What Entropy Means

In the context of password generation, entropy is a way to measure how many possibilities an attacker must consider when guessing a secret. It is usually measured in bits.

One bit means two equally likely possibilities. Two bits means four. Ten bits means 1,024. Every extra bit doubles the search space, so the numbers grow much faster than intuition suggests.

Entropy growth chartA black and white line chart showing that search space grows exponentially as entropy bits increase.01101K201M301B401T501Q+Entropy bitsSearch space
Illustrative curve: each added bit doubles the search space, so growth starts quietly and then rises sharply.

If a password is chosen uniformly at random from a set of possible passwords, the entropy is log2(number of possibilities). A random character chosen from 64 possible symbols gives 6 bits because 2^6 = 64.

This is why the method matters. A password is not strong because it looks complicated; it is strong because it was selected from a large set of possibilities in a way that attackers cannot predict.

A 16-character password drawn uniformly from a 64-character alphabet has about 96 bits of entropy. A 5-word Diceware-style passphrase from a 7,776-word list has about 64.6 bits because each word contributes log2(7,776), or about 12.9 bits.

A passphrase can be excellent because it is easier to remember and type, but a longer-looking phrase is not automatically stronger than a shorter random string. The number of random choices behind it is what matters.

The table below puts the same idea into concrete password and passphrase examples. The exact numbers matter less than the pattern: uniform generation and enough independent choices create the security margin.

ExampleSearch spaceEntropyInterpretation
12 random characters from 64 symbols64^1272 bitsStrong for many generated-password use cases
16 random characters from 64 symbols64^1696 bitsVery strong default for password-manager storage
5 Diceware words from 7,776 words7,776^564.6 bitsGood memorized-passphrase baseline
6 Diceware words from 7,776 words7,776^677.5 bitsStronger memorized passphrase

How the Browser Exposes Secure Randomness

Modern browsers expose cryptographic randomness through the Web Crypto API. The relevant function for password generation is crypto.getRandomValues(). You provide a typed array, such as a Uint8Array, and the browser fills it with cryptographically strong random values.

The Web Crypto API did not arrive everywhere at once, but browser-native cryptographic randomness has been broadly available for years. MDN marks the Crypto interface and crypto.getRandomValues() as baseline, widely available across browsers since July 2015.

Developer note: getRandomValues() is intended for small random buffers. The Web Crypto specification limits each call to 65,536 bytes, so larger consumers should request randomness in chunks.

In simplified form, it looks like this:

const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);

Those bytes are raw material. They are not yet a password. The next step is to convert them into characters, passphrase words, PIN digits, or API-key bytes.

That conversion step has to be done carefully. The next section explains one easy mistake, modulo bias, and how secure generators avoid it.

What passwords.lu Does Locally

The generator asks the browser for secure random bytes, checks that a secure source is available, and rejects catastrophic outputs such as a large buffer of all zeros or all identical bytes. This is not a replacement for the browser's random number generator; it is a simple guard against obvious failure states.

For random character passwords, bytes are mapped into the selected character set with rejection sampling. For passphrases, random values are mapped into fixed wordlists. For PINs and similar numeric secrets, the same principle applies: draw secure bytes, reject values that would create bias, then map the accepted values to the final alphabet.

The important point is that the generated password remains a local result of the page running in your browser.

Modulo Bias: The Small Mistake That Matters

A common implementation mistake is to take a random byte and reduce it with the modulo operator. For example, byte % 62 looks like an easy way to choose one character from a 62-character alphabet.

The problem is that 256 possible byte values do not divide evenly into 62 buckets. The first 8 character positions receive one extra byte value each, so those characters become slightly more likely than the others. That unevenness is called modulo bias.

For a toy script, the difference may look tiny. For a security tool, the correct standard is higher: every allowed character should be as close to uniformly likely as the random source permits. Small biases are also unnecessary; they are easy to avoid.

Rejection sampling is the usual fix. First compute the largest range that divides evenly by the alphabet size. For 62 characters, that range is 0 through 247, because 248 is divisible by 62. If the byte is 248 through 255, discard it and draw another byte. Values in the accepted range can then be mapped with byte % 62 without bias.

This is why a secure generator may draw more random bytes than the final password length. Some bytes are intentionally thrown away so the final password is not subtly skewed.

function randomIndex(byte, alphabetSize) {
  const max = Math.floor(256 / alphabetSize) * alphabetSize;
  if (byte >= max) return null; // reject and draw again
  return byte % alphabetSize;
}

The password generator in this codebase follows this pattern when mapping random bytes to password characters. The same idea is used for word indexes and numeric ranges: draw enough randomness, reject values that would create uneven buckets, and only then map to the final symbol.

Can User Input Add More Randomness?

Yes. A generator can mix browser randomness with additional user-provided entropy. This is not normally required when the browser and operating system are healthy, but it can be useful as a transparency feature and as defense-in-depth for users who do not fully trust the local random source.

One memorable example is the old "stir the pot" idea: the user moves through a small game or interaction, and the timing and direction of those actions are collected as another source of unpredictability.

A snake-style interaction can collect direction changes, timing between key presses, and movement history. Those values are not used as the only source of randomness. Instead, they are mixed with random bytes from crypto.getRandomValues().

This should be treated as defense-in-depth, not as permission to make the final password shorter. Moving a snake around for a minute does not make a 6-character password safe. Length, a large search space, and unbiased generation still matter.

A concrete example is passwordgenerator.ws, which uses a snake-style interaction to let the user stir additional input into the process.

How Mixing Two Sources Works

The right way to combine sources is not to concatenate them and hope for the best. A single hash call is also not the right full construction when you need a stream of random-looking material for generation. The better pattern is to use a key-derivation function that has an extraction step and an expansion step, such as HKDF.

The extraction step is not merely compression. Its job is to take input material that may be structured, non-uniform, partially known, or partly influenced by an attacker, and concentrate the available entropy into a short pseudorandom key. This is the core idea explained in Krawczyk's HKDF paper. The expansion step then derives as many bytes as the generator needs, while binding the output to a clear context such as password-generator/v1.

Conceptually, the flow is simple: browser random bytes and user-interaction bytes are fed into a KDF with context information; the KDF output is then expanded into bytes for the generation step.

If either source is strong and the KDF is used correctly, the output should remain unpredictable. User input can add a useful extra layer, but it should not be treated as a replacement for the browser's cryptographic random generator.

This is related to a broader cryptographic design principle: combining multiple sources can hedge against the failure of one component, but only if the combiner is designed carefully. The paper Many a Mickle Makes a Muckle studies this in the context of hybrid key exchange, where classical, post-quantum, and QKD-derived material are combined through a key schedule. Password generation is a much simpler setting, but the lesson is similar: mixing sources is useful only when the mixing construction is sound.

browserRandom = crypto.getRandomValues(...)
userRandom = collectKeyboardTimingAndMovement(...)
seed = HKDF-Extract(salt = browserRandom, inputKeyMaterial = userRandom)
bytes = HKDF-Expand(seed, info = "password-generator/v1" || context, length = neededBytes)
password = mapBytesToCharactersWithRejectionSampling(bytes)

In a production implementation, this must be done carefully: use a standard KDF, include context so values cannot be confused across purposes, request enough output bytes for the generator, and keep rejection sampling when mapping bytes into character sets.

Should You Distrust the Browser?

For normal use, the Web Crypto API is the right foundation. Browsers delegate randomness to the operating system's cryptographic random number generator, which is designed for exactly this type of security-sensitive task.

At the same time, no browser-based generator can protect against every local compromise. If the browser, operating system, extension environment, or device is already malicious, it can observe the page, alter JavaScript, read the clipboard, or capture keystrokes.

That is why the realistic security goal is layered: use a trustworthy browser, generate locally, use cryptographic randomness, and store the result in a password manager.

Avoid transmitting the secret when you can. When you do need to share it, use a secure transmission mechanism such as privatenote.ai, which we integrate in passwords.lu.

For practical channel choices, read our secure-sharing guide on what belongs in email, chat, secure notes, or a password manager.

Practical Takeaway

A good password generator should be boring in the right places. It should use cryptographic randomness, avoid biased character selection, generate locally, and explain its assumptions clearly.

Additional user input can be useful, but the baseline remains the same: use enough length, choose from a large alphabet or wordlist, map randomness without bias, and let the password come from a cryptographically secure random source rather than human imagination.

References

Generate a password locally

Try the passwords.lu generator and inspect the result locally in your browser. The generated password is not sent to our server.

Open Password Generator