Password Policy Guide — NIST 2024 Guidelines and Modern Best Practices
NIST SP 800-63B 2024 updated password guidelines: no mandatory rotation, no complexity rules, require minimum 15 characters, check against breached passwords. Learn modern...
NIST’s 2024 update to SP 800-63B reversed many traditional password policies. Forced rotation, complexity rules with special characters, and security questions are now explicitly discouraged. Here’s what modern password policy looks like.
Use the Password Generator to generate compliant passwords.
NIST SP 800-63B 2024 key changes
Dropped:
- Mandatory periodic password rotation (was: every 60-90 days)
- Complexity requirements like “must contain uppercase, digit, and symbol”
- Security questions as authentication factor
- SMS-based OTP as “sufficient” second factor (now “RESTRICTED”)
Added/strengthened:
- Minimum 15 characters for organizational accounts
- Check against breached password lists (Have I Been Pwned)
- Allow all printable ASCII + spaces
- Support paste into password fields
- No truncation of passwords
Minimum length
// Old convention: 8 characters
// NIST 2024: minimum 8 user-chosen, prefer 15+, allow up to 64+
function validatePasswordLength(password) {
if (password.length < 8) {
return { valid: false, message: 'Password must be at least 8 characters' };
}
if (password.length > 256) {
return { valid: false, message: 'Password must be 256 characters or fewer' };
}
return { valid: true };
}
// For higher-security contexts (enterprise, admin accounts):
const MIN_LENGTH = 15;
Check against breached passwords (HIBP)
Have I Been Pwned API uses k-Anonymity — you only send the first 5 chars of the SHA-1 hash:
import crypto from 'crypto';
async function isBreachedPassword(password) {
const hash = crypto.createHash('sha1').update(password).digest('hex').toUpperCase();
const prefix = hash.slice(0, 5);
const suffix = hash.slice(5);
const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`, {
headers: { 'Add-Padding': 'true' },
});
const text = await response.text();
const lines = text.split('\r\n');
for (const line of lines) {
const [hashSuffix, count] = line.split(':');
if (hashSuffix === suffix) {
return { breached: true, count: parseInt(count) };
}
}
return { breached: false, count: 0 };
}
// Usage:
const result = await isBreachedPassword('password123');
if (result.breached) {
console.log(`This password appeared ${result.count.toLocaleString()} times in data breaches`);
}
No complexity rules (but encourage length)
// OLD (bad) approach:
function hasComplexity(pw) {
return /[A-Z]/.test(pw) && /[a-z]/.test(pw) && /[0-9]/.test(pw) && /[^a-zA-Z0-9]/.test(pw);
}
// Forces users to make predictable substitutions: P@ssw0rd!
// NIST 2024: don't enforce complexity, but you MAY reject specific patterns:
function isCommonPattern(pw) {
const lower = pw.toLowerCase();
const common = ['password', '123456', 'qwerty', 'abc123', 'letmein', 'admin', 'welcome'];
return common.some(c => lower.includes(c));
}
// Instead, calculate and show strength:
function passwordStrength(pw) {
const patterns = {
hasUpper: /[A-Z]/.test(pw),
hasLower: /[a-z]/.test(pw),
hasDigit: /[0-9]/.test(pw),
hasSymbol: /[^a-zA-Z0-9]/.test(pw),
};
const charsetSize =
(patterns.hasLower ? 26 : 0) +
(patterns.hasUpper ? 26 : 0) +
(patterns.hasDigit ? 10 : 0) +
(patterns.hasSymbol ? 32 : 0);
const entropy = pw.length * Math.log2(Math.max(charsetSize, 1));
if (entropy < 40) return 'weak';
if (entropy < 60) return 'fair';
if (entropy < 80) return 'strong';
return 'very strong';
}
Allow paste and all characters
<!-- Don't disable paste! Users with password managers need it: -->
<input type="password" id="password" autocomplete="current-password">
<!-- Remove any onpaste="return false" attributes -->
<!-- Allow all printable ASCII including spaces: -->
<!-- No maxlength that's too short (use 256 as maximum) -->
<input type="password" id="password" maxlength="256">
Zxcvbn — realistic strength estimation
// npm install zxcvbn
import zxcvbn from 'zxcvbn';
const result = zxcvbn('correct horse battery staple');
// result.score: 0-4 (0=weak, 4=very strong)
// result.guesses: estimated number of guesses
// result.crack_times_display.online_throttling_100_per_hour: '23 centuries'
// result.feedback.suggestions: ['Add more words']
// Passphrase: 4 common words is stronger than a complex 8-char password
zxcvbn('password123!') // score: 2 (weak despite complexity)
zxcvbn('correct horse battery staple') // score: 4 (strong despite no symbols)
Related tools
- Password Generator — generate secure passwords
- Password Strength Checker — entropy and zxcvbn scoring
- Password Manager Guide — why use a password manager
Related posts
- How Secure Is My Password? Entropy, Crack Times, and What Actually Matters — Password security measured in bits of entropy, real hashcat benchmarks on RTX 40…
- Brute Force Password Attacks — How They Work and How to Defend Against Them — Brute force attacks try every possible password combination. Learn how attackers…
- Diceware Passphrases — Stronger and More Memorable Than Passwords — Diceware generates memorable passphrases by rolling dice to select words from a …
- Password Generator — How to Build a Secure Random Password Generator — Build a cryptographically secure password generator in JavaScript and Python. Le…
- Password Manager Guide — Why You Need One and How to Choose — Password managers store and generate strong unique passwords for every site. Her…
Related tool
Generate strong random passwords with configurable length, character classes, and exclusions. Real entropy meter, crack-time estimate, bulk mode.
Written by Mian Ali Khalid. Part of the Dev Productivity pillar.