Weak Password Hash

ID

python.weak_password_hash

Severity

critical

Resource

Cryptography

Language

Python

Tags

CWE:256, CWE:759, CWE:760, CWE:916, NIST.SP.800-53, OWASP:2021:A2, PCI-DSS:6.5.3

Description

Weak Password Hashing is a vulnerability that occurs when passwords are hashed using algorithms that are deemed insecure.

This can lead to the exposure of user passwords if an attacker gains access to hashed values and uses techniques such as brute force or dictionary attacks, using tools like Hashcat, L0phtcrack, or John The Ripper.

Rationale

Password hashing is a common technique used to secure user passwords by transforming them into a fixed-size string of characters, which ideally should be irreversible. However, if a weak or deprecated hashing algorithm is used, such as MD5 or SHA-1, attackers can more easily reverse-engineer or crack the hashed values.

Hash algorithms like MD5 and SHA-1 were widely used but are now considered insecure due to vulnerabilities found in their design that allow for collisions, where two different inputs produce the same hash, facilitating the cracking process.

To counter "rainbow table" attacks, which use pre-computed hash tables for password cracking, each password should be combined with a unique, random salt before hashing. This salt is stored alongside the hash.

Modern hardware can compute billions of hashes per second, making brute-force attacks on simple hashes feasible. The matter is to use strong and modern hash algorithms that are "slow enough", specifically designed for password hashing, such as Argon2, scrypt, bcrypt, or PBKDF2.

Modern password hashing algorithms DO NOT protect against (1) weak, easy-to-guess passwords, (2) when the same user password is reused across multiple systems, (3) against default known passwords, or (4) against hardcoding passwords or password leaking vulnerabilities. Your software must use proper password hashing and salt generation, but additional security measures such as multi-factor authentication are recommended.
import hashlib

password = 'passwordabc'
bytes = password.encode('utf-8')

# FLAW - Using a weak hashing algorithm (single iteration SHA-256) with a fixed salt
def hash_password_normal_hash():
    return hashlib.sha256(bytes).hexdigest() # FLAW

# FLAW - Using bcrypt password_hash with a weak cost value
import bcrypt

salt = bcrypt.gensalt(3) # FLAW

hash = bcrypt.hashpw(bytes, salt)

Remediation

To mitigate against password-guessing attacks, follow these best practices:

Recommended Password Hashing Scheme:

  • Use proven key stretching / password hashing algorithms, CPU- and memory-intensive hash functions, and : PBKDF2, bcrypt, scrypt, or Argon2.

  • For legacy systems where a specific password hashing algorithm cannot be used, use a common hash function (e.g., SHA-256) with many iterations (600,000 as of 2024).

  • Always use a long (at least 128-bit), random, unique salt for each password.

  • Configure work factor parameters to balance security and performance.

  • Optionally, add a secret "pepper" for additional security.

Which Hashing Algorithm to Use?:

Based on the most recent recommendations and research, Argon2id is the preferred modern password hashing algorithm for new implementations in 2025. Here is a ranking of the recommended algorithms, but your mileage may vary:

  • Argon2id: Winner of the 2015 Password Hashing Competition, Argon2id is considered the most secure and future-proof option. It offers a balanced approach to resisting both side-channel and GPU-based attacks.

  • scrypt: Excellent for systems requiring strong protection against hardware-based attacks, particularly in cryptocurrency applications. Originally designed for key derivation.

  • bcrypt: Still widely used and considered secure for general-purpose password hashing in web applications.

  • PBKDF2: While still used in some password managers and recommended by NIST, it is generally considered less secure than the above options for password hashing. For compliance with FIPS 140-1, PBKDF2 with SHA-256 or SHA-512 is recommended.

  • SHA-256 with large iteration counts is not recommended for password hashing, as it’s vulnerable to GPU-based attacks and lacks the memory-hardness of more modern algorithms. Use it as a fallback for legacy systems.

Common Vulnerabilities to Avoid:

Additional Considerations:

  • Users often reuse passwords across systems.

  • High-profile threat actors may use GPU accelerators or even specialized hardware like FPGAs to crack password hashes.

  • Use libraries that automate salt generation when possible.

  • If generating salt manually, use a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG).

  • Salt should be at least 128 bits.

  • Never use predictable information (e.g., username) as salt.

  • Implement constant-time comparison for hash verification to prevent timing attacks.

import hashlib

password = 'passwordabc'
bytes = password.encode('utf-8')

def hash_password_normal_hash():
    hash = hashlib.sha256(bytes).hexdigest()

    for _ in range(150000):
        hash = hashlib.sha256(hash.encode()).hexdigest()

    return hash

import bcrypt

salt = bcrypt.gensalt()

hash = bcrypt.hashpw(bytes, salt)

Configuration

The rule has the following configurable parameters:

  • thresholds, that indicates the different thresholds used for each of the hashing algorithms.

Example:

  # The thresholds used for each of the hashing algorithms
  thresholds:
    # Argon2d iterations
    - argon2.iterations: 3
    # Argon2d memory to use (in KiB)
    - argon2.mem: 12
    # Argon2d threads
    - argon2.parallel: 1

    # Scrypt parameters explained in https://words.filippo.io/the-scrypt-parameters/.
    # Recommended values are N=2^17, r=8, p=1
    # scrypt cpu factor (N), 131072 means 2^17 (recommended value for 2024)
    - scrypt.cpu: 131072
    # scrypt memory factor (r): the hash is 2r wider wrt. the core hash function (Salsa20 core). 8 means 1024 bytes
    # The real memory usage is 𝑁 × 2𝑟 × 64 = (128 × 𝑁 × 𝑟) bytes
    - scrypt.mem: 8
    # scrypt parallelization parameter (p), number of instances of mixing function that runs independently for salt to the final PBKDF2.
    - scrypt.parallel: 1

    # bcrypt strength
    - bcrypt.strength: 10

    # PBKDF2 iterations. NIST recommends 600,000 iterations for HMAC-SHA-256 as of 2020.
    # Never use less than 10,000
    - pbkdf2: 600000

    # Iterations for Legacy password hashing based on "fast" hash functions such as SHA-256
    - fast.hash: 150000

References