UNDER THE HOOD

How pwgen.hu Generates Passwords Without Sending Them Anywhere

Your browser downloads a small application, then generates the values on your device. A password is not requested from a server, and the generated result is not sent back. That design removes the server from the generation path—but it does not make your browser or device a trust-free environment.

In this article

From a command on a server to a tool on your device

The earlier pwgen.hu website used the Linux pwgen command on its server. The rebuilt site takes a different approach: static HTML, CSS, a locally bundled word list and JavaScript using the browser’s cryptographic random source. There is no PHP generation endpoint, database, account or password history.

That change matters because the server no longer needs to know a generated password in order to deliver it. It sends the same application files to everyone. The password generator, passphrase generator and other tools make their decisions locally, after those files arrive. Loading a page can still create an ordinary hosting access log; generated values and generator settings are not included in those requests.

The source of randomness

The core calls crypto.getRandomValues() to fill typed arrays with cryptographically strong random values. Browsers implement this through their cryptographic facilities; the application does not invent its own seed from the clock, pointer movement or a user name. MDN documents the API’s purpose and its use of a cryptographically suitable generator seeded with entropy. See MDN: getRandomValues().

Ordinary JavaScript Math.random() does not promise the unpredictability a secret needs. An output can look irregular without being suitable for a password. That is why the generation code never uses it, even as a fallback. If Web Crypto is unavailable, the controls fail with an explanation and no value is produced. MDN explicitly distinguishes Math.random() from cryptographic randomness.

A six-sided example of modulo bias

Imagine a perfectly uniform byte with 256 possible values, numbered 0 through 255. Taking the remainder after division by six seems like an easy die roll. But 256 is not divisible by six. Remainders 0, 1, 2 and 3 each have 43 input values, while 4 and 5 have only 42. Adding one would produce a subtly biased die.

Rejection sampling removes the uneven tail. Accept only 0 through 251, then take the remainder and add one. There are now 252 accepted inputs—42 for every face. If the byte is 252 through 255, discard it and draw again. The application applies the same idea to unsigned 32-bit values for normal bounded choices. The arbitrary-size helper used by constrained PIN generation masks unused bits and rejects integers outside the required interval.

Discarding a value is not a failure of randomness. It is the step that makes the mapping fair. This is useful for a 74-character pool, a 7,776-word list, a shuffle index or a custom die. You can try different ranges in the random number generator.

Required characters without fixed password positions

The default password needs at least one character from each selected group. The core selects those required characters first, fills the remaining positions from the complete allowed pool, and then applies a Fisher–Yates shuffle. At every shuffle step, the swap index comes from the same unbiased cryptographic helper. A required digit is therefore not permanently assigned to the end of the password.

Custom group minimums repeat the corresponding draws before filling. With repeated characters disabled, already used characters are removed from subsequent choices. If exclusions or minimums make a policy impossible, generation stops and the previous displayed secret is cleared. A rule that cannot be honored is never quietly weakened.

Shuffling hides the construction’s original positions, but it does not make every valid password equally likely. For example, an output with many digits may have more possible construction paths than another output. Our display therefore uses a conservative bound on min-entropy for these constrained passwords instead of presenting length × log₂(pool size) as an exact result.

Entropy belongs to the generation process

For N equally likely outcomes, theoretical entropy is log₂(N). An unconstrained length-L sequence from a pool of P symbols has Pᴸ outcomes, or L × log₂(P) bits. That calculation describes a process, not the appearance of a particular string. A familiar-looking result can arise by chance, and a complicated-looking human invention can still be predictable.

Our passphrase tool independently selects six words by default. With 7,776 entries, there are 7,776⁶ possible word sequences, giving about 77.5 bits. Separators and fixed capitalization add no random choices. The EFF recommends six words from its long list for most applications; the list is bundled here under its attribution license. Read EFF’s word-list guidance.

Other formats need their own accounting. UUID v4 reserves some bits for its format. Tokens distinguish raw bytes from encoded characters. The pronounceable tool counts choices in its two-letter units and any suffixes. These numbers are useful for comparing configurations, but they do not describe a service’s hashing cost or promise a universal time to crack.

What “local” protects—and what it cannot

The application does not log, transmit or persist generated values. It uses no cookies, analytics, localStorage or sessionStorage. Current results live in JavaScript memory and the visible output until replaced or the page is left. A batch replaces the previous batch. Plain-text export happens only when you press the download button, creating a local file that you must protect.

Copying hands the secret to your operating system’s clipboard. Clipboard history or cross-device synchronization may keep it longer than the page does. The app does not read the clipboard and cannot promise to erase copies elsewhere. If clipboard permission is denied, it shows a selected manual-copy field and reports the failure honestly.

Browser extensions with sufficient access can read page content. Malware can capture keystrokes or the screen. Someone looking over your shoulder can see an unmasked value. A compromised hosting account can serve altered JavaScript on a later visit. HTTPS helps protect delivery in transit, but it does not turn compromised application code into trustworthy code. The Web Crypto specification’s security considerations make clear that the API is only one part of an application’s security environment.

JavaScript strings are managed by the browser, so replacing a result is not a claim of secure memory erasure. The privacy guarantee is about application behavior: no secret storage or transmission is designed into this tool.

Offline use and deliberate updates

After a successful first visit, a service worker caches an explicit list of static pages, scripts, styles, icons and the EFF word list. The application can then generate values without network access. It does not cache a rendered password or a form submission. All nine generators and these articles are part of the offline shell.

When a new version is available, the old one can keep working until you choose to update. The update message warns that current displayed values will be cleared, and controlled tabs reload onto the new shell. Save anything you need through your password manager before updating. Browser storage eviction can remove the offline cache, so “available offline” depends on the cached installation remaining intact. MDN explains the service-worker lifecycle and caching model.

Why there is no cosmic-randomness requirement

The normal generator already relies on local Web Crypto. It does not contact a quantum, cosmic-noise or other external randomness service. A network dependency would add availability, provenance and privacy questions without fixing a known problem in the normal generation path.

A future educational experiment could combine supplemental input with fresh local randomness using a reviewed, domain-separated key-derivation construction such as HKDF. The local source must remain the secret foundation; external bytes must never be the sole source or overwrite it. That design would rely on cryptographic assumptions and need tests, timeouts and an explicit failure path. RFC 5869 describes HKDF’s extract-and-expand construction. No such integration is presented as working today.

The practical first step is less exotic: generate a unique password, store it safely, and use the receiving service’s additional protections. Choose random characters for compact storage, random words for manual entry, or raw random tokens for an application that needs them.

Put it into practice.