Securely Hash and Verify Passwords
A secure password hashing skill for Argon2id, bcrypt, and scrypt with work-factor calibration and legacy hash migration.
Maintainer of this project? Claim this page to edit the listing.
1.0.0Add to Favorites
Why it matters
Implement robust password security by hashing and verifying user credentials using industry-standard algorithms like Argon2id, bcrypt, and scrypt. Ensure compliance with best practices to prevent common vulnerabilities and protect sensitive data.
Outcomes
What it gets done
Implement secure password hashing with Argon2id, bcrypt, or scrypt.
Generate cryptographically secure salts for each password.
Perform constant-time password verification to prevent timing attacks.
Audit and migrate legacy password hashing schemes.
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/vb-secure-password-hasher | bash Overview
Secure Password Hasher
A secure password hashing skill covering Argon2id, bcrypt, and scrypt implementations in Python, Node.js, and Java, with work-factor calibration. It also covers rate limiting and legacy hash migration. Use it when implementing or auditing password storage that needs a modern adaptive hashing algorithm and calibrated work factors.
What it does
This skill implements secure password storage using industry-standard algorithms - Argon2, bcrypt, and scrypt - covering salt generation, timing-attack prevention, and modern security standards compliance. Core requirements are: never store plaintext passwords, use a cryptographically secure salt of at least 16 bytes per password, employ an adaptive hashing function, configure work factors that balance security and performance, and implement constant-time comparison during verification. Algorithm priority favors Argon2id (winner of the Password Hashing Competition, memory-hard) first, bcrypt second (well-established and widely supported), scrypt third, and PBKDF2 only as a legacy-compatibility fallback.
It provides working implementations in three languages: Python with Argon2id (a hasher class supporting hash, verify, and needs-rehash checks), Node.js with bcrypt (configurable salt rounds, currently recommended at 12-14), and Java with Spring Security's BCryptPasswordEncoder. Security configuration covers a benchmarking script to calibrate Argon2 parameters to a 250ms-1s target hashing time, and environment-specific settings - stronger parameters (time cost 4, 128MB memory, parallelism 2) for production versus faster settings for development. Advanced measures cover rate-limited verification (locking out an identifier after 5 failed attempts within a configurable window) and a password-migration pattern that verifies against a legacy hash (like MD5) on login and transparently upgrades to the new secure hash.
class SecurePasswordHasher:
def __init__(self):
# Argon2id parameters (adjust based on security requirements)
self.ph = PasswordHasher(
time_cost=3, # Number of iterations
memory_cost=65536, # Memory usage in KiB (64MB)
parallelism=1, # Number of parallel threads
hash_len=32, # Hash output length
salt_len=16 # Salt length
)
def hash_password(self, password: str) -> str:
"""Hash a password securely with Argon2id"""
return self.ph.hash(password)
When to use - and when NOT to
Use this skill when implementing or auditing password storage - choosing an algorithm (Argon2id, bcrypt, or scrypt), calibrating work factors, adding rate limiting and lockout, or migrating users off a legacy hash algorithm.
It is not a fit as a substitute for broader authentication system design (session management, MFA, account recovery flows) - it's scoped specifically to how a password is hashed, salted, and verified, not the surrounding auth architecture.
Inputs and outputs
Inputs are the plaintext password to hash or verify, and your target environment (production versus development work factors). Outputs are a securely hashed password string, a boolean verification result with timing-attack protection, and, where applicable, a migrated hash and rate-limit lockout status.
Who it's for
Backend engineers implementing password storage who need concrete, language-specific implementations of Argon2id, bcrypt, or scrypt rather than building hashing logic from scratch - following OWASP guidelines (minimum 250ms hashing time, 128-bit salt, 256-bit hash output) and avoiding named pitfalls: never using MD5, SHA-1, or plain SHA-256, never implementing custom hashing algorithms, avoiding predictable or global salts, never logging plaintext passwords or hashes, and never hardcoding hash parameters in application code.
Source README
Secure Password Hasher
You are an expert in cryptographic password hashing, specializing in implementing secure password storage systems using industry-standard algorithms like Argon2, bcrypt, and scrypt. You understand the critical security principles of password hashing, salt generation, timing attack prevention, and compliance with modern security standards.
Core Security Principles
Password Hashing Requirements
- Never store plaintext passwords - Always hash before storage
- Use cryptographically secure salt - Minimum 16 bytes of random data per password
- Employ adaptive hashing functions - Argon2id (preferred), bcrypt, or scrypt
- Configure appropriate work factors - Balance security and performance
- Implement constant-time comparison - Prevent timing attacks during verification
Algorithm Selection Priority
- Argon2id - Winner of Password Hashing Competition, memory-hard function
- bcrypt - Well-established, widely supported, time-tested
- scrypt - Memory-hard alternative, good for specific use cases
- PBKDF2 - Only as fallback for legacy compatibility
Implementation Best Practices
Argon2id Implementation (Recommended)
### Python with argon2-cffi
import secrets
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
class SecurePasswordHasher:
def __init__(self):
# Argon2id parameters (adjust based on security requirements)
self.ph = PasswordHasher(
time_cost=3, # Number of iterations
memory_cost=65536, # Memory usage in KiB (64MB)
parallelism=1, # Number of parallel threads
hash_len=32, # Hash output length
salt_len=16 # Salt length
)
def hash_password(self, password: str) -> str:
"""Hash a password securely with Argon2id"""
return self.ph.hash(password)
def verify_password(self, password: str, hashed: str) -> bool:
"""Verify password against hash with timing attack protection"""
try:
self.ph.verify(hashed, password)
return True
except VerifyMismatchError:
return False
def needs_rehash(self, hashed: str) -> bool:
"""Check if hash needs updating due to parameter changes"""
return self.ph.check_needs_rehash(hashed)
bcrypt Implementation
// Node.js with bcrypt
const bcrypt = require('bcrypt');
class PasswordHasher {
constructor() {
// Cost factor - increase as hardware improves
// Current recommendation: 12-14 rounds
this.saltRounds = 12;
}
async hashPassword(password) {
try {
const salt = await bcrypt.genSalt(this.saltRounds);
return await bcrypt.hash(password, salt);
} catch (error) {
throw new Error('Password hashing failed');
}
}
async verifyPassword(password, hashedPassword) {
try {
return await bcrypt.compare(password, hashedPassword);
} catch (error) {
// Always return false on error to prevent timing attacks
return false;
}
}
getRounds(hashedPassword) {
return bcrypt.getRounds(hashedPassword);
}
}
Java Implementation with BCrypt
// Java with Spring Security BCrypt
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
public class SecurePasswordService {
private final PasswordEncoder passwordEncoder;
public SecurePasswordService() {
// Strength 12 = 2^12 iterations
this.passwordEncoder = new BCryptPasswordEncoder(12);
}
public String hashPassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
public boolean verifyPassword(String plainPassword, String hashedPassword) {
return passwordEncoder.matches(plainPassword, hashedPassword);
}
}
Security Configuration Guidelines
Work Factor Calibration
### Benchmark to determine appropriate parameters
import time
from argon2 import PasswordHasher
def benchmark_argon2_parameters():
test_[REDACTED]
configs = [
{'time_cost': 2, 'memory_cost': 32768}, # 32MB
{'time_cost': 3, 'memory_cost': 65536}, # 64MB
{'time_cost': 4, 'memory_cost': 131072}, # 128MB
]
for config in configs:
ph = PasswordHasher(**config)
start_time = time.time()
ph.hash(test_password)
duration = time.time() - start_time
print(f"Config {config}: {duration:.3f}s")
### Target: 250ms-1s hashing time
benchmark_argon2_parameters()
Environment-Specific Settings
### Production configuration
password_hashing:
algorithm: "argon2id"
argon2:
time_cost: 4
memory_cost: 131072 # 128MB
parallelism: 2
bcrypt:
rounds: 13
### Development configuration (faster)
password_hashing:
algorithm: "argon2id"
argon2:
time_cost: 2
memory_cost: 32768 # 32MB
parallelism: 1
bcrypt:
rounds: 10
Advanced Security Measures
Rate Limiting and Brute Force Protection
import time
from collections import defaultdict
from threading import Lock
class RateLimitedPasswordVerifier:
def __init__(self, max_attempts=5, lockout_duration=300):
self.max_attempts = max_attempts
self.lockout_duration = lockout_duration
self.attempts = defaultdict(list)
self.lock = Lock()
self.hasher = SecurePasswordHasher()
def verify_with_rate_limit(self, identifier, password, hashed_password):
with self.lock:
now = time.time()
# Clean old attempts
self.attempts[identifier] = [
attempt_time for attempt_time in self.attempts[identifier]
if now - attempt_time < self.lockout_duration
]
if len(self.attempts[identifier]) >= self.max_attempts:
return False # Account locked
# Verify password
is_valid = self.hasher.verify_password(password, hashed_password)
if not is_valid:
self.attempts[identifier].append(now)
else:
# Clear attempts on successful login
self.attempts[identifier] = []
return is_valid
Password Hash Migration
class PasswordMigrator:
def __init__(self):
self.new_hasher = SecurePasswordHasher()
def migrate_on_login(self, password, old_hash, hash_type):
"""Migrate password hash during user login"""
if self.verify_legacy_hash(password, old_hash, hash_type):
# Generate new secure hash
new_hash = self.new_hasher.hash_password(password)
return {'verified': True, 'new_hash': new_hash}
return {'verified': False, 'new_hash': None}
def verify_legacy_hash(self, password, hash_value, hash_type):
if hash_type == 'md5':
# Legacy MD5 verification (insecure, migrate immediately)
import hashlib
return hashlib.md5(password.encode()).hexdigest() == hash_value
# Add other legacy hash handlers as needed
return False
Compliance and Standards
OWASP Guidelines Implementation
- Minimum 250ms hashing time on server hardware
- Use random salt of at least 128 bits (16 bytes)
- Store hash output of at least 256 bits (32 bytes)
- Implement secure password policies alongside hashing
- Log authentication attempts without exposing sensitive data
Common Pitfalls to Avoid
- Never use MD5, SHA-1, or plain SHA-256 for passwords
- Don't implement custom hashing algorithms
- Avoid predictable salts or global salts
- Never log plaintext passwords or hashes
- Don't use deprecated bcrypt implementations
- Avoid hardcoding hash parameters in application code
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.