โ† Back to Blog

Password Security Tips for Developers

2026-04-17 ยท 5 min read

Never Implement Password Hashing Yourself

One of the most dangerous anti-patterns in password security is developers implementing password hashing algorithms themselves. Common mistakes include: using MD5 or SHA-1 (too fast, suitable for file integrity checks but not password storage); unsalted hashing (no defense against rainbow table attacks); using the generic hash function of an off-the-shelf encryption library (not optimized for password storage); implementing a custom "more secure" scheme (almost guaranteed to have design flaws).

The correct approach is to use algorithm libraries specifically designed for password storage. These algorithms (bcrypt, Argon2, scrypt, PBKDF2) are deliberately designed to be "slow," resisting GPU cracking by increasing computation time and memory requirements. As a developer, your job is to choose the right algorithm and configure parameters correctly, not to implement it yourself.

Recommended implementations by major language and framework:

# Python - using bcrypt
import bcrypt
# Hash
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# Verify
bcrypt.checkpw(password.encode(), hashed)

# Python - using Argon2 (recommended for new projects)
from argon2 import PasswordHasher
ph = PasswordHasher()
hashed = ph.hash(password)
ph.verify(hashed, password)  # Verify

# Node.js - using bcrypt
const bcrypt = require('bcrypt');
const hashed = await bcrypt.hash(password, 12);  // cost factor=12
await bcrypt.compare(password, hashed);  // Verify

# Go - using golang.org/x/crypto/bcrypt
import "golang.org/x/crypto/bcrypt"
hashed, _ := bcrypt.GenerateFromPassword([]byte(password), 12)
bcrypt.CompareHashAndPassword(hashed, []byte(password))

# PHP - using built-in functions (PHP 5.5+)
$hashed = password_hash($password, PASSWORD_ARGON2ID);
password_verify($password, $hashed);

Algorithm selection guidance: Argon2id is the first choice for new projects (NIST-recommended, resistant to both GPU and side-channel attacks); use bcrypt (work factor 12 or above) where broad compatibility is needed; never use MD5, SHA-1, or unsalted SHA-256/SHA-512 in new projects.

Password Policy Design: Following New NIST Guidelines

Based on NIST SP 800-63B, modern password policies should: require minimum length (at least 8 characters, 12+ recommended); support maximum length of at least 64 characters (don't cap at 20 or shorter); support all Unicode characters (including emoji, Chinese characters, spaces); not mandate special characters or mixed case (these rules backfire); not mandate periodic rotation (unless a breach is detected); check whether the password appears in known breach lists (integrable with Have I Been Pwned API).

A note on password maximum length: some developers think limiting password length (like 256 characters) prevents denial-of-service attacks (by submitting extremely long passwords to consume hashing computation). This is a valid concern. The solution is to truncate the password before hashing (e.g., take the first 72 bytes), or set a reasonable upper limit (like 1,024 characters) and handle oversized input at validation, rather than limiting users to shorter passwords.

Designing a Secure Password Reset Flow

Password reset is one of the easiest parts of an authentication system to have security vulnerabilities. Key points for a secure password reset flow: use high-entropy one-time tokens (generated with CSPRNG, not user IDs or timestamps); set short expiration times (15โ€“60 minutes, not 24 hours or never expiring); one-time use (immediately invalidate the token after use); log security events after use; never transmit passwords in plaintext in URLs or emails; never email a new password (send a reset link and let the user set their own new password).

A common mistake is using "security questions" (pet names, birth city, etc.) as the verification method for password reset. The answers to these questions are typically obtainable from public information or social engineering, offering very low security. The modern approach is to use one-time tokens delivered via email or SMS, combined with TOTP authenticators, rather than relying on security questions.

Developers need to proactively guard against these common password-related vulnerabilities: timing attacks โ€” use constant-time comparison functions (like hmac.compare_digest() or bcrypt.checkpw()) for password comparison, not ordinary string comparison; SQL injection โ€” always use parameterized queries, never concatenate passwords or usernames into SQL statements; leaking passwords in transit โ€” ensure all password-related interfaces transmit only over HTTPS, and HTTP Strict Transport Security (HSTS) headers are set; logging passwords โ€” audit all logging code to ensure password fields, Authorization headers, and Cookies don't appear in log files.

Another often-overlooked risk: keeping plaintext copies of passwords in memory for extended periods. After processing a password (hashing or verifying), the variables containing the password should be cleared as soon as possible. Some languages (like Java and Python) make this harder due to immutable string memory management, but it's still worth attention in high-security scenarios.

Implementing Multi-Factor Authentication

Regardless of how well password storage is done, MFA should be provided to users (and encouraged). For most applications, TOTP (Time-based One-Time Password) is the best choice: wide authenticator app support (Google Authenticator, Authy); doesn't depend on the phone network (SMS has SIM-swapping risks); standardized (RFC 6238) and easy to integrate. Implement TOTP using existing libraries like Python's pyotp, Node's speakeasy, or framework-level solutions.

If your application targets enterprise users, supporting WebAuthn/FIDO2 (hardware security keys, like YubiKey) provides the highest level of security and is the only reliable defense against phishing attacks. WebAuthn has native support in all major browsers, and implementation costs are much lower than five years ago.

Password and Secrets Management

As a developer, you need to protect not only user passwords but also the secrets used by the application itself: database passwords, API keys, encryption keys, OAuth client secrets, etc. These secrets should be managed through dedicated key management services (KMS) or secret storage systems (like HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager) rather than hardcoded in configuration files or source code.

In local development environments, use .env files to store secrets (ensure they're excluded in .gitignore) and load them into environment variables through libraries like dotenv. In CI/CD pipelines, use the platform's encrypted environment variables (like GitHub Secrets, GitLab CI Variables) rather than hardcoding in configuration files. In production environments, use the professional key management services mentioned above.

Try the free tool now

Use Free Tool โ†’