NIST Password Guidelines
NIST Password Guidelines
The National Institute of Standards and Technology (NIST) revolutionized password security guidance with Special Publication 800-63B, abandoning outdated complexity requirements in favor of evidence-based recommendations. These guidelines, while not legally binding for private organizations, represent best practices that many regulations reference and courts consider when evaluating reasonable security measures.
NIST's key recommendations include minimum 8-character passwords (with longer requirements for higher security), checking against compromised password lists, removing arbitrary complexity rules, eliminating mandatory password rotation, and supporting all printable ASCII and Unicode characters. These guidelines acknowledge that user-hostile requirements often decrease security by encouraging poor password choices or insecure workarounds.
import requests
import hashlib
from typing import Set, Tuple, Optional
import sqlite3
from bloom_filter2 import BloomFilter
class NISTCompliantPasswordChecker:
"""NIST SP 800-63B compliant password checking"""
def __init__(self, min_length: int = 8,
check_breach_list: bool = True,
allow_common_passwords: bool = False):
self.min_length = min_length
self.check_breach_list = check_breach_list
self.allow_common_passwords = allow_common_passwords
# Initialize breach checking
if self.check_breach_list:
self.breach_checker = BreachedPasswordChecker()
# Load common passwords list
if not self.allow_common_passwords:
self.common_passwords = self._load_common_passwords()
def check_password(self, password: str,
username: Optional[str] = None) -> Tuple[bool, List[str]]:
"""Check password against NIST guidelines"""
issues = []
# NIST: Minimum length requirement (8 chars for most uses)
if len(password) < self.min_length:
issues.append(f"Password must be at least {self.min_length} characters")
# NIST: Maximum length at least 64 characters
if len(password) > 256: # Generous maximum
issues.append("Password exceeds maximum length")
# NIST: Support all printable ASCII and Unicode
# (No character restrictions)
# NIST: Check against breach lists
if self.check_breach_list:
if self.breach_checker.is_breached(password):
issues.append("Password found in breach database")
# NIST: Check against common passwords
if not self.allow_common_passwords:
if password.lower() in self.common_passwords:
issues.append("Password is too common")
# NIST: Context-specific checks
if username:
if username.lower() in password.lower():
issues.append("Password contains username")
# NIST: No composition rules required
# (No complexity requirements like special chars, etc.)
return len(issues) == 0, issues
def _load_common_passwords(self) -> Set[str]:
"""Load list of common passwords"""
# In production, load from comprehensive list
# This is a minimal example
common = {
'password', '123456', 'password123', 'admin', 'letmein',
'welcome', 'monkey', '1234567890', 'qwerty', 'abc123',
'Password1', 'password1', '123456789', 'welcome123',
'12345678', '12345', '1234567', 'sunshine', 'iloveyou'
}
return {pwd.lower() for pwd in common}
class BreachedPasswordChecker:
"""Check passwords against breach databases"""
def __init__(self, use_bloom_filter: bool = True):
self.use_bloom_filter = use_bloom_filter
if use_bloom_filter:
# Bloom filter for memory-efficient checking
# In production, pre-populate with breach data
self.bloom = BloomFilter(
max_elements=1000000000, # 1 billion passwords
error_rate=0.001 # 0.1% false positive rate
)
self._populate_bloom_filter()
def is_breached(self, password: str) -> bool:
"""Check if password appears in breach databases"""
# Option 1: Check against local bloom filter
if self.use_bloom_filter:
return password in self.bloom
# Option 2: Use Have I Been Pwned API (k-anonymity)
return self._check_hibp_api(password)
def _check_hibp_api(self, password: str) -> bool:
"""Check password against Have I Been Pwned API"""
# Hash password with SHA-1 (HIBP requirement)
sha1_hash = hashlib.sha1(password.encode()).hexdigest().upper()
prefix = sha1_hash[:5]
suffix = sha1_hash[5:]
try:
# Query API with k-anonymity
response = requests.get(
f'https://api.pwnedpasswords.com/range/{prefix}',
headers={'User-Agent': 'NIST-Compliant-Checker'}
)
if response.status_code == 200:
# Check if our suffix appears in results
hashes = response.text.splitlines()
for line in hashes:
hash_suffix, count = line.split(':')
if hash_suffix == suffix:
return True
return False
except Exception as e:
# Log error but don't block password creation
logging.error(f"HIBP API check failed: {e}")
return False
def _populate_bloom_filter(self):
"""Populate bloom filter with known breached passwords"""
# In production, load from breach databases
# This is a demonstration
breached_samples = [
'password', '123456', 'password123', 'admin', 'letmein'
]
for pwd in breached_samples:
self.bloom.add(pwd)
class ModernPasswordPolicy:
"""NIST SP 800-63B based password policy"""
def __init__(self):
self.checker = NISTCompliantPasswordChecker()
def generate_policy_text(self) -> str:
"""Generate user-friendly policy based on NIST guidelines"""
return """
Password Requirements:
• Minimum 8 characters (12+ recommended)
• Maximum 256 characters
• All characters allowed (letters, numbers, symbols, spaces)
• No specific character types required
• Cannot be a commonly used password
• Cannot contain your username
• Cannot be a previously breached password
Tips for Strong Passwords:
• Use a passphrase: "coffee sunrise mountain bicycle"
• Make it memorable but unique to you
• Consider using a password manager
• Longer is stronger than complex
What We DON'T Require:
✗ Special characters (though you can use them)
✗ Regular password changes (unless compromised)
✗ Security questions
✗ Password hints
"""
def validate_password_change(self, old_password: str,
new_password: str) -> Tuple[bool, List[str]]:
"""Validate password change per NIST guidelines"""
issues = []
# Check basic requirements
valid, basic_issues = self.checker.check_password(new_password)
issues.extend(basic_issues)
# NIST: Don't require regular rotation
# Only check if passwords are different
if old_password == new_password:
issues.append("New password must be different from current password")
# NIST: No arbitrary lifetime restrictions
# Password changes should be event-driven (breach, compromise, etc.)
return len(issues) == 0, issues
NIST guidelines emphasize usability alongside security. Arbitrary complexity requirements and forced rotation often lead to predictable patterns (Password1!, Password2!, etc.) that decrease security. Instead, length provides the primary defense against brute force attacks, while breach checking prevents known-compromised passwords. This approach acknowledges that security measures must work with human behavior, not against it.