Every data breach story starts the same way — someone used "password123" or their dog's name followed by a birth year. I've been building auth systems for over a decade, and it still blows my mind how many people (developers included) treat passwords like an inconvenience rather than the one thing standing between their data and the entire internet. Most passwords out there? Crackable in seconds. Not minutes. Seconds. Let me walk you through what actually makes a password strong, how generators work under the hood, and what I wish every developer understood about handling credentials in their apps.
What Makes a Password Strong?
Password strength is measured by entropy — a measure of unpredictability. The higher the entropy, the harder the password is to guess or crack. Entropy is determined by two factors: the size of the character set used and the length of the password.
The formula for entropy is: E = L × log₂(N), where L is the password length and N is the number of possible characters.
| Character Set | Size (N) | Entropy per char |
|---|---|---|
| Lowercase only (a–z) | 26 | 4.7 bits |
| Lowercase + uppercase | 52 | 5.7 bits |
| Alphanumeric (a–z, A–Z, 0–9) | 62 | 5.95 bits |
| Full printable ASCII (incl. symbols) | 95 | 6.57 bits |
A 16-character password using the full ASCII character set has approximately 105 bits of entropy — making it effectively impossible to brute-force with current technology.
Common Password Mistakes
❌ Using dictionary words
Dictionary attacks can crack common words in seconds, even with letter substitutions like 'p@ssw0rd'.
❌ Short passwords (under 8 characters)
Short passwords have low entropy and can be brute-forced quickly, even with complex characters.
❌ Reusing passwords across sites
If one site is breached, attackers use credential stuffing to try the same password on other services.
❌ Using personal information
Names, birthdays, and pet names are easily guessable through social engineering or public profiles.
❌ Predictable patterns
Patterns like 'Password1!' or 'Qwerty123' are among the first tried in attacks.
How Password Generators Work
A secure password generator uses a cryptographically secure pseudorandom number generator (CSPRNG) to select characters from a defined character set. Unlike regular random number generators, CSPRNGs produce output that is statistically indistinguishable from true randomness and cannot be predicted even if previous outputs are known.
In browsers, the crypto.getRandomValues() API provides cryptographically secure randomness. Our password generator uses this API to ensure every generated password is genuinely unpredictable.
Key options in a good password generator:
- Length: Aim for at least 16 characters for general use, 20+ for high-security accounts.
- Character sets: Include uppercase, lowercase, numbers, and symbols for maximum entropy.
- Exclude similar characters: Optionally exclude visually similar characters like
0,O,l,1to avoid transcription errors. - Exclude ambiguous characters: Remove characters that may cause issues in certain systems.
Best Practices for Developers
If you are building an application that handles passwords, follow these security standards:
✅ Never store plain-text passwords
Always hash passwords before storing them. Use a dedicated password hashing algorithm like bcrypt, Argon2, or scrypt — never MD5 or SHA-1.
✅ Use salted hashes
Add a unique random salt to each password before hashing to prevent rainbow table attacks.
✅ Enforce minimum password requirements
Require at least 12 characters. Consider checking against known breached password lists using the HaveIBeenPwned API.
✅ Implement rate limiting
Limit login attempts to prevent brute-force attacks. Use exponential backoff and account lockout policies.
✅ Use HTTPS everywhere
Passwords must only be transmitted over encrypted connections. Never send passwords over HTTP.
✅ Recommend password managers
Encourage users to use a password manager so they can use unique, strong passwords for every account.
Password Security in Web Applications
Building a web application that handles user passwords comes with serious responsibilities. A single security mistake can expose your users' credentials and damage trust permanently. This section covers the practical implementation details every developer needs to know.
Implementing Password Strength Meters
A password strength meter gives users real-time feedback as they type, encouraging stronger choices without being overly restrictive. The key is to measure actual entropy rather than just checking for character types. A password like Tr0ub4dor&3 scores well on character variety but is weaker than a random 16-character string.
The zxcvbn library by Dropbox is the gold standard for client-side password strength estimation. It uses pattern matching and frequency lists to estimate crack time, returning a score from 0 (very weak) to 4 (very strong) along with feedback messages. It catches common patterns like keyboard walks (qwerty), dates, and dictionary words with substitutions.
import zxcvbn from "zxcvbn"
const result = zxcvbn("Tr0ub4dor&3")
// result.score → 3 (strong)
// result.crack_times_display.offline_slow_hashing_1e4_per_second
// → "centuries"
// result.feedback.suggestions → ["Add another word or two"]OWASP Password Guidelines
The Open Web Application Security Project (OWASP) publishes authoritative guidelines for password security. Their current recommendations include:
- Minimum password length of 8 characters, with a maximum of at least 64 characters.
- Allow all printable ASCII characters and Unicode — do not restrict character types.
- Do not require periodic password changes unless there is evidence of compromise.
- Check new passwords against a list of known breached passwords (e.g., via the HaveIBeenPwned API).
- Provide a password strength meter rather than arbitrary complexity rules.
- Allow paste in password fields — blocking paste discourages password manager use.
Multi-Factor Authentication
Even the strongest password can be compromised through phishing, data breaches at other sites, or malware. Multi-factor authentication (MFA) adds a second layer of verification that an attacker cannot obtain just by knowing the password. Common MFA methods include:
TOTP (Time-based One-Time Password)
Apps like Google Authenticator and Authy generate a 6-digit code that changes every 30 seconds. Standardized in RFC 6238.
Hardware security keys (FIDO2/WebAuthn)
Physical devices like YubiKey provide the strongest MFA. Immune to phishing because the key is bound to the specific domain.
SMS codes
Convenient but the weakest MFA option — vulnerable to SIM swapping attacks. Better than no MFA, but not recommended for high-security applications.
Push notifications
Apps like Duo send a push notification to the user's phone. Easy to use but requires an internet connection.
How Credential Stuffing Attacks Work
Credential stuffing is one of the most common and effective attacks against web applications. When a site is breached and its password database is leaked, attackers take those username/password pairs and automatically try them against hundreds of other sites. Because many users reuse passwords, a significant percentage of attempts succeed.
Defenses against credential stuffing include: rate limiting login attempts, requiring CAPTCHA after failed attempts, monitoring for unusual login patterns (new device, new location), and most importantly — encouraging users to use unique passwords via a password manager.
Why Password Managers Are Essential
The only practical way to use strong, unique passwords for every account is to use a password manager. Bitwarden is a popular open-source option with free tier support and end-to-end encryption. 1Password is a premium option widely used by development teams, with features like shared vaults and SSH key management. Both generate strong random passwords and autofill them across devices.
As a developer, you should recommend password managers to your users and design your application to work well with them — which means allowing paste in password fields, using standard autocomplete attributes, and not blocking browser autofill.
Generate a Strong Password Now
Use our free browser-based password generator — no data is sent to any server:
Password Managers and Modern Authentication
The human brain is not designed to remember dozens of unique, high-entropy passwords. This fundamental limitation is why password reuse remains the number one cause of credential-based attacks. Password managers solve this problem completely, and emerging standards like passkeys (WebAuthn) are poised to eliminate passwords entirely. This section covers the modern authentication landscape every developer should understand.
Why Password Managers Solve the Human Memory Problem
The average person has 80-100 online accounts. Remembering a unique, strong password for each is impossible without help. The result is predictable: people reuse passwords, use weak patterns, or write them on sticky notes. A password manager eliminates this entirely by storing all passwords in an encrypted vault protected by a single master password. Users only need to remember one strong password — the manager handles everything else.
Studies show that password manager users have significantly stronger passwords, near-zero password reuse, and faster login times (autofill is faster than typing). From a security engineering perspective, a password manager converts the problem from "100 weak passwords" to "1 strong password + encrypted storage."
How Password Managers Generate and Store Passwords
Password managers use cryptographically secure random number generators to create passwords with maximum entropy. The generated passwords are stored in an encrypted database (vault) that uses strong symmetric encryption — typically AES-256-GCM or XChaCha20-Poly1305. The encryption key is derived from the master password using a key derivation function like PBKDF2, Argon2, or scrypt.
// How a password manager derives the encryption key
// (simplified — real implementations have additional layers)
// 1. User enters master password
const masterPassword = "correct-horse-battery-staple"
// 2. Derive encryption key using Argon2id
const salt = getUserSalt() // unique per user, stored server-side
const encryptionKey = await argon2.hash(masterPassword, {
type: argon2.argon2id,
memoryCost: 65536, // 64MB — makes brute-force expensive
timeCost: 3,
salt: salt,
hashLength: 32 // 256-bit key
})
// 3. Decrypt the vault with AES-256-GCM
const vault = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: storedIV },
encryptionKey,
encryptedVaultData
)
// 4. Vault is now accessible — passwords are decrypted in memory only
// Never written to disk unencryptedBiometric Authentication
Modern devices support biometric authentication — fingerprint sensors, facial recognition, and iris scanning — as a convenient alternative to typing passwords. Biometrics serve as a local unlock mechanism: they verify that the person holding the device is authorized, then release a stored cryptographic key that performs the actual authentication.
Important: biometrics are not passwords. They cannot be changed if compromised, they can be spoofed with varying difficulty, and they are best used as one factor in multi-factor authentication rather than as a standalone credential. Apple's Face ID and Touch ID, Android's BiometricPrompt, and Windows Hello all follow this model — the biometric unlocks a secure enclave that holds the actual authentication key.
Passkeys (WebAuthn): The Future of Authentication
Passkeys represent a fundamental shift away from passwords entirely. Based on the WebAuthn standard (FIDO2), passkeys use public-key cryptography: a unique key pair is generated for each site, the private key stays on your device (or syncs via iCloud/Google), and the public key is stored on the server. Authentication is a cryptographic challenge-response — no password is ever transmitted or stored.
// WebAuthn registration (creating a passkey)
const credential = await navigator.credentials.create({
publicKey: {
challenge: serverChallenge, // random bytes from server
rp: { name: "Example App", id: "example.com" },
user: {
id: userId,
name: "alice@example.com",
displayName: "Alice"
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 }, // ES256 (recommended)
{ type: "public-key", alg: -257 } // RS256 (fallback)
],
authenticatorSelection: {
residentKey: "required", // discoverable credential
userVerification: "required" // biometric or PIN
}
}
})
// → Sends public key to server for storage
// WebAuthn authentication (using a passkey)
const assertion = await navigator.credentials.get({
publicKey: {
challenge: serverChallenge,
rpId: "example.com"
}
})
// → Sends signed challenge to server for verificationPasskeys are phishing-resistant by design — the credential is bound to the specific domain, so a fake login page on a different domain cannot trigger the passkey. They are also resistant to credential stuffing (no password to reuse) and server breaches (the server only stores public keys, which are useless to attackers).
Multi-Factor Authentication Best Practices
Multi-factor authentication (MFA) adds layers of security beyond the password. The factors are typically categorized as: something you know (password), something you have (device or key), and something you are (biometric). Effective MFA combines at least two different categories.
- Offer MFA on registration, not just settings: Users are most likely to enable MFA during account creation. Make it a prominent step in your onboarding flow.
- Support multiple MFA methods: Offer TOTP, hardware keys, and backup codes. Users who lose one method need a recovery path.
- Make backup codes visible only once: Generate 8-10 single-use backup codes during MFA setup. Show them once, require the user to save them, and hash them before storing.
- Implement step-up authentication: Require MFA for sensitive actions (password changes, payment modifications) even if the user is already logged in.
Why SMS 2FA Is Weaker Than TOTP Authenticator Apps
SMS-based two-factor authentication is significantly weaker than TOTP (Time-based One-Time Password) authenticator apps like Google Authenticator, Authy, or 1Password. SMS is vulnerable to multiple attack vectors that TOTP is immune to:
⚠️ SIM swapping
An attacker convinces your carrier to transfer your number to their SIM card. They then receive all SMS codes sent to you. This attack requires only social engineering — no technical expertise.
⚠️ SS7 network interception
The SS7 protocol used by phone networks has known vulnerabilities that allow attackers to intercept SMS messages in transit. Nation-state actors and organized crime groups actively exploit this.
⚠️ Malware on the device
SMS messages can be read by any app with SMS permissions on Android. A malicious app can silently forward 2FA codes to an attacker.
⚠️ Number recycling
When you change phone numbers, your old number is reassigned to someone else — who then receives your 2FA codes.
TOTP authenticator apps are immune to all of these attacks because the secret is stored locally on the device and codes are generated offline using a shared secret and the current time. The code never travels over a network. For maximum security, use hardware security keys (FIDO2/WebAuthn) which are immune to both phishing and device compromise.
For Developers: Implementing Password Security Correctly
If you're building an application that handles passwords, here's the implementation checklist I've refined over years of security audits. Get these right and you'll pass any pentest:
// Node.js — Password hashing with bcrypt (production-ready)
const bcrypt = require("bcrypt")
// Registration: hash the password before storing
async function registerUser(email, plainPassword) {
// Validate minimum requirements
if (plainPassword.length < 8) throw new Error("Password too short")
if (plainPassword.length > 72) throw new Error("Password too long for bcrypt")
// Hash with cost factor 12 (2^12 = 4096 iterations)
const hash = await bcrypt.hash(plainPassword, 12)
// Store hash in database — NEVER store plainPassword
await db.users.create({ email, passwordHash: hash })
}
// Login: compare submitted password against stored hash
async function loginUser(email, plainPassword) {
const user = await db.users.findByEmail(email)
if (!user) {
// ⚠️ Still run bcrypt.compare to prevent timing attacks
await bcrypt.compare(plainPassword, "$2b$12$invalidhashplaceholder")
throw new Error("Invalid credentials")
}
const isValid = await bcrypt.compare(plainPassword, user.passwordHash)
if (!isValid) throw new Error("Invalid credentials")
return user
}
// Password reset — use crypto-secure random token
const crypto = require("crypto")
function generateResetToken() {
return crypto.randomBytes(32).toString("hex") // 256-bit random token
}
// ⚠️ NEVER do these:
// ❌ bcrypt.hashSync(password, 12) — blocks the event loop
// ❌ Storing password in plain text or MD5/SHA-256
// ❌ Using the same "Invalid email" vs "Invalid password" error message
// (different messages leak whether an email is registered)Password Policy: What to Enforce and What to Skip
NIST 800-63B (the current gold standard for password guidelines) recommends against many rules that websites still enforce. Here's what modern password policy should look like:
| Rule | ✅ Do | ❌ Don't |
|---|---|---|
| Minimum length | 8 characters minimum (12+ recommended) | Allow less than 8 characters |
| Maximum length | Allow at least 64 characters | Limit to 16 or 20 characters |
| Complexity rules | Allow any characters including spaces | Require uppercase + number + symbol (frustrates users) |
| Breach checking | Check against known breached passwords (HaveIBeenPwned API) | Skip breach checking |
| Expiration | Don't force periodic rotation (NIST says it weakens security) | Require password change every 90 days |
| Password hints | Don't offer password hints | Store hints (they leak information) |
| Paste in password field | Allow paste (password managers need it!) | Disable paste on password inputs |
The biggest revelation from NIST's updated guidelines: forced password rotation actually weakens security. When users are forced to change passwords every 90 days, they choose weaker passwords and follow predictable patterns (Password1!, Password2!, Password3!). Let users keep strong passwords indefinitely and only force a change if a breach is detected.
How Attackers Crack Passwords (Know Your Enemy)
Understanding how passwords are cracked helps you understand why certain practices matter:
🔴 Credential stuffing
How it works: Attackers take username/password pairs from breached sites and try them on other sites. Works because 65% of people reuse passwords.
Defense: Use unique passwords per site. Rate limit login attempts. Implement breach detection.
🔴 Dictionary attacks
How it works: Try common words, names, dates, and known leaked passwords. 'password123', 'letmein', 'admin2026' all fall instantly.
Defense: Use random generated passwords. Check against breach databases during registration.
🔴 Brute force (GPU-accelerated)
How it works: Modern GPUs can test billions of hash combinations per second. A 6-character password falls in seconds against MD5.
Defense: Use slow hash functions (bcrypt/Argon2). Minimum 12+ character passwords.
🔴 Rainbow tables
How it works: Pre-computed hash lookup tables. If your password's hash matches one in the table, it's cracked instantly.
Defense: Always use salted hashes. bcrypt/Argon2 include salts automatically.
🔴 Phishing
How it works: Fake login pages that look identical to real ones. User enters their real password on the fake site.
Defense: Use hardware security keys (FIDO2). They verify the domain — won't work on fake sites.
🔴 Keyloggers
How it works: Malware records every keystroke, including passwords as you type them.
Defense: Use MFA. Even with the password captured, attacker needs the second factor.
Summary
Look, I could wrap this up with a neat list of rules, but here's what it really comes down to: use a password manager. That's the real answer. No human brain is remembering 50+ unique random strings. Pick Bitwarden, 1Password, whatever — just stop reusing passwords. Generate them at 16+ characters, let the manager deal with the mess, and spend your energy on things that actually matter. If you're building apps, hash with bcrypt or Argon2 and don't overthink it.
