Skip to content

CWE-916: Use of Password Hash With Insufficient Computational Effort

Overview

Weak password hashing occurs when applications use fast cryptographic hash functions (MD5, SHA-1, SHA-256) or inadequately configured password hashing algorithms to store passwords. Unlike general-purpose hashing, password hashing requires intentionally slow, computationally expensive algorithms to resist brute force attacks. When attackers obtain a database dump, weak password hashes can be cracked to reveal plaintext passwords, which users often reuse across multiple sites.

OWASP Classification

A04:2025 - Cryptographic Failures

Risk

Weak password hashing creates severe security risks:

  • Mass password compromise: Attackers crack passwords from stolen databases
  • Credential stuffing: Cracked passwords reused on other sites (users reuse passwords)
  • Account takeover: Attackers gain full access to user accounts
  • Privilege escalation: Administrative passwords compromised along with regular users
  • Privacy violations: Access to personal data, communications, financial information
  • Compliance failures: Violates PCI-DSS, HIPAA, GDPR, SOC 2 requirements
  • Reputational damage: Public disclosure of password breach
  • Legal liability: Lawsuits from affected users

According to breach reports, 80%+ of passwords hashed with MD5 or SHA-1 are cracked within hours.

The Problem with Fast Hashes

Fast hash functions are vulnerable to password cracking:

  • Speed: Modern GPUs can compute billions of SHA-256 hashes per second, making brute force feasible
  • Rainbow tables: Pre-computed hash tables enable instant lookups for common passwords
  • Brute force: Trying all possible passwords (up to certain length) becomes computationally trivial
  • Dictionary attacks: Testing millions of common passwords completes in seconds

According to breach reports, 80%+ of passwords hashed with MD5 or SHA-1 are cracked within hours.

Remediation Steps

Core principle: Password hashing must use adaptive, purpose-built algorithms (Argon2id/bcrypt/scrypt) with work factors tuned to current hardware - slow enough to make brute force attacks impractical (target 250-500ms per hash) while remaining fast enough for legitimate authentication. Never use fast general-purpose hashes (MD5, SHA-1, SHA-256) for passwords.

Locate the weak password hashing in your code

  • Review the flaw details to identify the specific file, line number, and code pattern
  • Identify which weak hashing algorithm is in use (MD5, SHA-1, SHA-256, unsalted hash, etc.)
  • Trace the password flow from user registration/login through hashing to storage
  • Check database schema: review password hash column and verify if salt column exists

Replace with adaptive password hashing algorithms (Primary Defense)

Replace fast hashes with purpose-built password hashing functions:

RECOMMENDED (in priority order):

1. Argon2id

  - Resistant to GPU/ASIC attacks (memory-hard and CPU-hard)
  - Configuration: 19MiB memory, 2 iterations, 1 parallelism (OWASP minimum)
  - Configurable: 47 MiB memory, 3-4 iterations for sensitive applications

2. bcrypt

  - CPU-hard, resistant to GPUs
  - Work factor: 12 (minimum), 14 (recommended for 2024)
  - Automatic salt generation, max password length: 72 bytes

3. scrypt

  - Good GPU resistance via memory requirements
  - Configuration: N=2^17, r=8, p=1 minimum

4. PBKDF2-HMAC-SHA256

  - Use ≥ 600,000 iterations (OWASP 2023 recommendation)
  - 1,300,000+ for high-security applications

AVOID: MD5, SHA-1, SHA-256, SHA-512 (too fast), plain SHA-2 without key stretching, custom "salted hash" implementations, unsalted hashes

Configure adequate work factors and implement proper salting

  • Tune algorithm parameters: Target 250-500ms per hash on production servers, test on actual hardware, increase work factor annually
  • Configure work factors: Argon2id (19-47 MiB memory, 2-4 iterations), bcrypt (work factor 12-14), PBKDF2 (600,000+ iterations)
  • Implement proper salts: Generate new random salt for EVERY password (128 bits minimum), use cryptographically secure RNG, store salt alongside hash
  • Use vetted libraries: bcrypt, Argon2, scrypt handle salting automatically - no manual salt handling required

Implement migration strategy for existing password hashes

  • Add version tracking: Add "hash_version" column to database to track which algorithm was used
  • Implement dual-read verification: On user login, verify password against old hash (MD5/SHA-256) first, if valid immediately re-hash with new algorithm (bcrypt/Argon2)
  • Update on successful login: Replace old hash with new hash and update version in database
  • Monitor migration progress: Track percentage of users migrated to new algorithm
  • Set migration deadline: After 90 days, force password reset for inactive accounts still using weak hashes
  • Remove legacy support: Once 95%+ migrated, stop accepting old algorithm hashes

Monitor and audit password hashing usage

  • Log password hashing operations for security monitoring (registration, login, password change)
  • Alert on usage of deprecated algorithms if dual-read is still active
  • Track migration progress with database queries (SELECT hash_version, COUNT(*) FROM users GROUP BY hash_version)
  • Review code for remaining instances of weak hashing (grep for MD5, SHA256, etc.)
  • Monitor hash computation time to ensure work factor is adequate

Test the password hashing changes thoroughly

  • Test migration in staging with production data copy
  • Verify user login works with both old and new hashes during migration
  • Test password registration creates new hashes with proper algorithm
  • Verify hash computation time meets 250-500ms target
  • Test rollback procedures if migration causes issues
  • Re-scan with security scanner to confirm the issue is resolved

Migration Considerations

CRITICAL: Changing password hashing algorithms will prevent users from logging in with their existing passwords unless you implement a migration strategy.

What Breaks

  • Existing password hashes become invalid: Users cannot authenticate with their current passwords
  • All login attempts fail: Changing from MD5/SHA-256 to bcrypt means old hashes won't match new algorithm
  • User lockout: Without migration, users must reset passwords via email recovery
  • Support burden: Influx of "can't log in" tickets if not handled properly
  • Business impact: E-commerce checkout abandonment, SaaS service interruption, customer frustration

Migration Approach

Dual-Read Strategy (Strongly Recommended)

Gradually upgrade passwords as users log in:

  1. Add algorithm version tracking: Store which algorithm was used for each password hash
  2. Implement dual verification: Check password against both old (MD5/SHA-256) and new (bcrypt/Argon2) algorithms
  3. Upgrade on successful login: When user logs in with old hash successfully, immediately re-hash with new algorithm and update database
  4. Monitor migration progress: Track percentage of users migrated to new algorithm
  5. Set migration deadline: After 90 days, force password reset for inactive accounts still using weak hashes
  6. Remove legacy support: Once 95%+ migrated, stop accepting old algorithm hashes

Big-Bang Migration (NOT Recommended)

Force all users to reset passwords:

  • Invalidate all existing password hashes
  • Send password reset emails to entire user base
  • Mark all accounts as requiring password reset
  • Impact: All users locked out until they reset via email. High support burden, poor user experience.

Rollback Procedures

If migration causes issues:

  1. Code rollback: Revert application to previous version that supports old algorithm
  2. Database rollback: Restore from backup if hashes were modified
  3. Emergency access: Temporarily re-enable dual-read if old algorithm support was removed too early
  4. Communication plan:

    • Status page: "Investigating login issues"
    • User email: "Temporary login problems resolved"
    • Support team: "Use password reset for affected users"

Testing Recommendations

Pre-deployment testing:

  • Test migration in staging with production data copy
  • Verify old passwords still work (dual-read)
  • Verify old passwords upgrade after successful login
  • Verify new passwords work immediately
  • Test failed login attempts don't break migration
  • Verify migration tracking returns correct percentages
  • Test password reset flow for un-migrated users

Post-deployment monitoring:

  • Monitor login success/failure rates (should remain constant)
  • Track migration progress (% of users upgraded)
  • Alert on authentication error rate increase
  • Monitor support tickets for login issues
  • Track time-to-migrate (estimate completion)

Key metrics to track:

  • Total users in database
  • Users on new algorithm vs. old algorithm
  • Daily migration rate
  • Authentication error rates
  • Projected completion date

Testing and Verification

Verify Strong Algorithm

# Test that bcrypt/Argon2 is actually used
from bcrypt import hashpw, gensalt

# Hash should start with $2b$ (bcrypt) or $argon2id$ (Argon2)
password = b"testpassword"
hash = hashpw(password, gensalt(rounds=12))
print(hash)  # Should show: b'$2b$12$...'

Measure Hash Time

# Hash operation should take 250-500ms

time measure_hash_duration()

# Expected: 250ms-500ms per hash
# Too fast (< 100ms): Increase work factor
# Too slow (> 1s): Decrease work factor or use faster hardware

Test Salt Uniqueness

Create multiple hashes of same password:

hash1 = hash("password123")
hash2 = hash("password123")

# Hashes should be different (different salts)

assert hash1 != hash2

# But both should verify correctly

assert verify("password123", hash1) == True
assert verify("password123", hash2) == True

Code Review Verification

# Search for weak hashing algorithms

grep -r "MD5\|SHA1\|SHA-256" --include="*.java" --include="*.py" --include="*.cs"

# Verify password-specific hashing is used

grep -r "bcrypt\|argon2\|scrypt\|PBKDF2" --include="*.java" --include="*.py"

Expected result: No fast hashes for passwords; only bcrypt/Argon2/scrypt/PBKDF2.

Database Inspection

-- Check stored password hashes

SELECT LEFT(password_hash, 10), COUNT(*) 
FROM users 
GROUP BY LEFT(password_hash, 10);

-- Expected patterns:
-- $2b$12$... (bcrypt)
-- $argon2id$ (Argon2)
-- pbkdf2_sha256$ (Django PBKDF2)

-- Should NOT see:
-- 5f4dcc3b5a (MD5 - 32 hex chars)
-- 5baa61e4c9 (SHA-1 - 40 hex chars)

Automated Security Testing

  • Use password audit tools (hashcat, john the ripper) against test hashes
  • Verify weak hashes are cracked quickly, strong hashes resist cracking
  • Include in security test suite

Additional Resources