Hey fellow devs! In a world where data breaches make headlines weekly, weak passwords are like leaving your front door unlocked in a sketchy neighborhood. We've all been there—scrambling to create a "strong" password on the fly, only to end up with something forgettable and crackable. What if I told you you could build a lightning-fast, ultra-secure password generator that runs entirely in the browser, without sending a single byte to a server?
That's exactly what I did with PasswordGenerator.cam, a free tool powered by the Web Crypto API. It's customizable, truly random, and respects user privacy from the get-go. In this article, I'll walk you through how I built it, why it's secure, and how you can fork or extend it for your own projects. Let's dive in—no fluff, just code and crypto.
The Problem: Why Most Password Generators Fall Short
Before we code, let's set the stage. Traditional password generators often:
- Rely on server-side randomness, which can introduce biases or logging risks.
- Use pseudo-random number generators (PRNGs) like
Math.random(), which aren't cryptographically secure and can be predicted under certain attacks. - Lack customization without bloating the UI.
According to NIST guidelines (SP 800-63B), passwords should be at least 8 characters but ideally longer, with a mix of character sets for entropy. But entropy is king—true randomness ensures a password like X7!pQ2#kL9$mV4 isn't guessable in a million years.
Enter the Web Crypto API: A browser-native interface for cryptographic operations, including crypto.getRandomValues(). It's hardware-backed where possible (e.g., via TPM on desktops) and uniform across modern browsers. No npm packages needed—just vanilla JS.
Step 1: Setting Up the Basics (HTML + CSS)
We start simple. A static HTML page with inputs for customization and a generate button. Here's the skeleton:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secure Password Generator</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
input[type="range"] { width: 100%; }
button { background: #007bff; color: white; padding: 10px 20px; border: none; cursor: pointer; }
#password { font-family: monospace; font-size: 1.2em; word-break: break-all; }
</style>
</head>
<body>
<h1>Generate a Bulletproof Password</h1>
<label>Length: <span id="lengthValue">16</span></label>
<input type="range" id="length" min="8" max="200" value="16">
<label><input type="checkbox" id="uppercase" checked> Uppercase Letters</label>
<label><input type="checkbox" id="lowercase" checked> Lowercase Letters</label>
<label><input type="checkbox" id="numbers" checked> Numbers</label>
<label><input type="checkbox" id="symbols" checked> Symbols</label>
<button onclick="generatePassword()">Generate</button>
<p>Generated: <span id="password"></span></p>
<script src="script.js"></script>
</body>
</html>
This gives us a clean UI. The range slider for length (8-200 chars—overkill? Maybe, but why not?) and checkboxes for charset selection. Pro tip: Default to "on" for all to encourage strong defaults.
Step 2: The Magic—Generating Secure Randomness in JS
Now, the core logic in script.js. We use crypto.getRandomValues() to fill a Uint8Array, then map bytes to characters. This ensures uniform distribution—no server pings, no data leaks.
function generatePassword() {
const length = parseInt(document.getElementById('length').value);
const uppercase = document.getElementById('uppercase').checked;
const lowercase = document.getElementById('lowercase').checked;
const numbers = document.getElementById('numbers').checked;
const symbols = document.getElementById('symbols').checked;
// Build the charset based on selections
let charset = '';
if (uppercase) charset += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if (lowercase) charset += 'abcdefghijklmnopqrstuvwxyz';
if (numbers) charset += '0123456789';
if (symbols) charset += '!@#$%^&*()_+-=[]{}|;:,.<>?';
if (charset.length === 0) {
alert('Select at least one character type!');
return;
}
// Generate secure random bytes
const randomBytes = new Uint8Array(length);
crypto.getRandomValues(randomBytes);
// Map bytes to charset indices
let password = '';
for (let i = 0; i < length; i++) {
const randomIndex = randomBytes[i] % charset.length;
password += charset[randomIndex];
}
document.getElementById('password').textContent = password;
document.getElementById('lengthValue').textContent = length; // Update slider display
}
// Update length display on slider change
document.getElementById('length').addEventListener('input', function() {
document.getElementById('lengthValue').textContent = this.value;
});
Key Security Notes:
-
crypto.getRandomValues()is the gold standard for CSPRNG in browsers. It throws an error in insecure contexts (e.g., non-HTTPS), which is a feature, not a bug. - We modulo the byte value against charset length for even distribution. For extra pedantry, you could use rejection sampling to avoid bias, but for passwords, this is plenty secure (entropy ~ log2(charset^length)).
- No clipboard API here to avoid auto-copy pitfalls—users copy manually for awareness.
Test it: Open in Chrome DevTools, generate a few, and log password.length. Boom—randomness in <1ms.
Step 3: Enhancements for Dev Delight
To make it production-ready (like on PasswordGenerator.cam), I added:
-
Strength Meter: Use zxcvbn (a lightweight library) to score entropy. Include via CDN:
<script src="https://cdnjs.cloudflare.com/ajax/libs/zxcvbn/4.4.2/zxcvbn.min.js"></script>.
// After generating...
const strength = zxcvbn(password).score;
const meter = document.createElement('div');
meter.textContent = `Strength: ${['Weak', 'Fair', 'Good', 'Strong', 'Unbreakable'][strength]}`;
meter.style.color = strength > 2 ? 'green' : 'red';
document.body.appendChild(meter); // Or integrate into UI
- Export/QR Code: For mobile, generate a QR of the password using a lib like qrcode.js.
- PWA Support: Add a manifest.json for offline use—perfect for "generate on the go."
Edge cases? Handle short charsets gracefully and warn if entropy dips below 80 bits (NIST rec).
Why Browser-Only Wins for Privacy & Performance
Servers add latency and trust issues—why log user prefs? With static hosting (GitHub Pages, Netlify), your site loads in milliseconds. Web Crypto is audited, cross-browser (Chrome 37+, Firefox 21+), and future-proof.
On PasswordGenerator.cam, we've served thousands without a single backend call. Users love the speed; devs appreciate the open-source vibe (fork it on GitHub!).
Wrapping Up: Secure Your Side Projects (and the World)
Building this took an afternoon, but the impact? Lifelong better habits for users. Next time you're auditing auth flows, drop in a custom generator—it's a quick win.
Try it live at passwordgenerator.cam and let me know: What's your go-to charset combo? Drop a comment below, share your forks, or tweet me @pgdotcam with improvements.

Top comments (0)