The Developer's Guide to Password Security
Password security vulnerabilities cause some of the most damaging data breaches in the industry. The patterns that lead to them — MD5 password hashing, unsalted hashes, storing plaintext passwords, weak secrets management — are surprisingly common even in professional codebases. This guide gives you the foundational knowledge to handle credentials correctly from day one.
Never Store Passwords as MD5
The most common password security mistake is hashing passwords with MD5 (or SHA-1, or SHA-256). The problem isn't that these algorithms are wrong in general — SHA-256 is excellent for file integrity checks. The problem is that they're fast. A modern GPU can compute billions of MD5 hashes per second.
If an attacker steals your password database and finds an MD5 hash like 5f4dcc3b5aa765d61d8327deb882cf99, they can crack it in under a second using a rainbow table lookup (that's "password", by the way). Even without rainbow tables, brute-forcing an unsalted MD5 hash of a common password takes milliseconds.
# WRONG — never do this
import hashlib
stored_hash = hashlib.md5(password.encode()).hexdigest()
# ALSO WRONG — SHA-256 is still too fast for passwords
stored_hash = hashlib.sha256(password.encode()).hexdigest()
Proper Password Hashing with bcrypt
bcrypt was designed in 1999 specifically for password hashing. It has a configurable work factor (cost) that lets you tune how slow it is — slow enough to deter brute force, fast enough for legitimate login. It automatically generates and stores a salt, preventing rainbow table attacks.
# Python — using bcrypt
import bcrypt
# Hashing (at registration)
password = b"user_password"
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# Store 'hashed' in the database
# Verification (at login)
if bcrypt.checkpw(password, hashed):
print("Password matches")
# Node.js — using bcryptjs
const bcrypt = require('bcryptjs');
const hash = await bcrypt.hash(password, 12);
const match = await bcrypt.compare(password, hash);
Argon2id is the recommended algorithm for new systems — it won the 2015 Password Hashing Competition and is memory-hard (resists GPU and ASIC attacks far better than bcrypt). Use the argon2-cffi library in Python or argon2id in Node.js.
# Python — using Argon2id
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=2, memory_cost=65536, parallelism=2)
hash = ph.hash("user_password")
ph.verify(hash, "user_password") # raises VerifyMismatchError if wrong
What is Password Entropy?
Entropy measures how unpredictable a password is, expressed in bits. A password with N bits of entropy would take on average 2N-1 guesses to crack by brute force. Entropy depends on both the length of the password and the size of the character set used:
- Lowercase only (26 chars): ~4.7 bits per character
- Lowercase + uppercase (52 chars): ~5.7 bits per character
- Alphanumeric (62 chars): ~5.95 bits per character
- Full printable ASCII (95 chars): ~6.57 bits per character
A random 12-character password using full ASCII has ~79 bits of entropy — sufficient for most purposes. A passphrase of 5 random common words has ~65 bits of entropy but is far more memorable. The key word is random — human-chosen passwords have far less entropy than the mathematics suggests because humans are predictable.
Generate cryptographically random passwords with the Password Generator at Tools.Fun, which uses your browser's crypto.getRandomValues() API for true randomness.
Password Policy Best Practices
Counterintuitively, many traditional password policies (mandatory complexity rules, forced periodic rotation) actively harm security. NIST's current guidelines (SP 800-63B) recommend:
- Minimum 8 characters; allow up to 64+ characters (bcrypt is limited to 72 bytes — consider pre-hashing with SHA-256 for very long passwords)
- Check passwords against known-breached password lists (HaveIBeenPwned API)
- Do not require complexity rules — they lead to predictable patterns (
Password1!) - Do not force periodic rotation unless there's evidence of compromise
- Allow copy-paste (don't disable it — it encourages password manager use)
P@ssw0rd1.Secrets Management for Applications
Application secrets (database passwords, API keys, encryption keys) should never be hardcoded in source code. The minimal acceptable approach is environment variables, which keep secrets out of version control. Better approaches for production:
- AWS Secrets Manager / Parameter Store — secrets stored encrypted, accessed via IAM-controlled API calls with automatic rotation support
- HashiCorp Vault — open-source secrets management with dynamic secret generation, lease management, and audit logging
- Kubernetes Secrets — base64-encoded (not encrypted at rest by default; enable encryption at rest or use an external secrets operator)
- .env files — acceptable for local development, never commit to version control. Add
.envto.gitignoreimmediately.
# Load secrets from environment (Python)
import os
db_password = os.environ['DB_PASSWORD'] # Raises KeyError if not set
# Safer — fails loudly with a clear message
db_password = os.environ.get('DB_PASSWORD')
if not db_password:
raise RuntimeError("DB_PASSWORD environment variable is required")
API Key Generation
API keys should be generated using a cryptographically secure random number generator. They should be long enough (at least 128 bits / 16 bytes of entropy) to be unguessable, and should be stored hashed in your database (just like passwords) so that a database breach doesn't expose all your API keys.
# Python — generate a secure API key
import secrets
api_key = secrets.token_urlsafe(32) # 256 bits of entropy, URL-safe
# Store only the hash
import hashlib
stored_hash = hashlib.sha256(api_key.encode()).hexdigest()
# At verification time: hash the presented key and compare
Service Account Credentials
Service accounts (non-human accounts used by applications and automated systems) are often the weakest link. Common mistakes: shared credentials between environments, credentials committed to Git, overly broad permissions, credentials that never expire. Use short-lived tokens where possible (OAuth 2.0 client credentials with short-lived JWTs), rotate long-lived credentials regularly, and audit access logs for anomalous service account activity.
Generate Secure Passwords
The Password Generator at Tools.Fun creates cryptographically random passwords with configurable length and character sets. The Hash Generator lets you verify MD5 and SHA hashes for testing and learning purposes — but remember, never use MD5 or SHA for production password storage.
← Back