Skill

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.


91
Spark score
out of 100
Updated 7 months ago
Version 1.0.0
Models

Add 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

01

Implement secure password hashing with Argon2id, bcrypt, or scrypt.

02

Generate cryptographically secure salts for each password.

03

Perform constant-time password verification to prevent timing attacks.

04

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

  1. Argon2id - Winner of Password Hashing Competition, memory-hard function
  2. bcrypt - Well-established, widely supported, time-tested
  3. scrypt - Memory-hard alternative, good for specific use cases
  4. 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.