Skip to content

CWE-328: Use of Weak Hash

Overview

Weak cryptographic hashes fail in two different ways, and each needs a different fix.

MD5 and SHA-1 are broken against collisions - an attacker can construct two inputs with the same digest, cheaply for MD5 and at practical cost for SHA-1 since the 2017 SHAttered result and the 2020 chosen-prefix attack. That is what forges a certificate or a signature, and what lets a benign document and a malicious one share a checksum. It is a problem wherever the hash stands in for the identity of something an attacker supplies. The fix is SHA-256 or better.

Fast hashes are a different weakness for password storage, and collisions have nothing to do with it. MD5, SHA-1 and plain SHA-256 are all fast by design, so an attacker holding a database dump guesses candidate passwords at billions per second on commodity GPUs until a digest matches. Neither MD5 nor SHA-1 has a practical preimage attack, so replacing them with SHA-256 does not slow that down - only a deliberately slow function with a tunable work factor does. The fix is Argon2id, scrypt or bcrypt, in that order; see CWE-916. The first two are also memory-hard, which blunts the GPU and ASIC advantage, and is why bcrypt sits behind them.

Relationship to Other CWEs

OWASP Classification

A04:2025 - Cryptographic Failures

Risk

High: Collision weakness in MD5 and SHA-1 breaks anything that treats a digest as an identity - forged certificates and code signatures, a malicious file passing a published checksum, deduplication and cache keys that can be made to alias, and commit or artifact identifiers that no longer pin what they name.

Speed is the separate risk, and it lands on password storage: a stolen table of MD5, SHA-1 or plain SHA-256 digests is cracked offline at billions of guesses per second, and every reused password recovered from it is a working credential somewhere else. Where the same digests are also unsalted, pre-computed rainbow tables reduce that to a lookup and one cracked entry breaks every account sharing the password - see CWE-759.

Remediation Steps

Core Principle: Use appropriate cryptographic hashes; never use fast hashes for passwords or security-critical derivations.

Locate the weak hash usage

  • Find the call the finding points at and note which hash it uses: MD5, SHA-1, or plain SHA-256 applied to a password
  • Work out what the digest is for - password storage, file integrity, a digital signature, an HMAC, a checksum. The replacement depends on the answer
  • Follow what is being hashed and where the digest goes afterwards

Use strong hashes for different purposes (Primary Defense)

  • For Passwords:
    • Use Argon2id first, then scrypt where Argon2id is unavailable. Both are memory-hard; Configure appropriate work factors below has the parameters
    • bcrypt is for legacy systems where neither is available. Cap or pre-hash the input: bcrypt reads at most 72 bytes, and runtimes disagree about what happens beyond that - PHP's password_hash silently ignores the rest, so two passphrases sharing their first 72 bytes authenticate interchangeably, while Python's bcrypt 5.x raises
    • PBKDF2-HMAC-SHA256 where a FIPS-validated implementation is required
    • Never MD5, SHA-1 or plain SHA-256. They are fast, and fast is what an offline guessing attack needs
  • For Integrity/Checksums:
    • Use SHA-256, SHA-384, SHA-512, SHA-3, BLAKE2, or BLAKE3. SHA-3 and the Keccak-256 that ships in Ethereum tooling are not interchangeable - FIPS 202 changed the padding, so the two produce different digests for the same input
    • Not MD5 or SHA-1, which both have practical collision attacks
  • For Message Authentication (shared secret):
    • Use HMAC-SHA256 or stronger (HMAC-SHA384, HMAC-SHA512)
    • Do not use HMAC-MD5 in new designs. MD5 collisions do not break HMAC-MD5 the way they break plain MD5, but MD5 is deprecated regardless
  • For Digital Signatures (public key):
    • Sign a SHA-256 or stronger digest with RSA-PSS, ECDSA on P-256 or larger, or Ed25519
    • HMAC is not a signature, and it is the usual wrong substitute here. Both parties hold the same key, so either can produce a tag the other accepts: it authenticates a message between two parties who already trust each other and gives a third party no reason to believe either of them. Where the verifier must be able to check a value without also being able to forge it - software releases, receipts, audit records, anything a dispute could turn on - the mechanism is a signature

Understand hash use cases and select appropriately

Which of these jobs the digest is doing decides the replacement:

  • Passwords: a slow, adaptive function - Argon2id, then scrypt, with bcrypt for legacy systems and PBKDF2 where a FIPS-aligned environment requires it
  • File integrity: a collision-resistant hash - SHA-256 for checksums and file verification
  • Message authentication: HMAC over a strong hash - HMAC-SHA256 for API signatures and JWTs
  • Digital signatures: SHA-256 or stronger under RSA or ECDSA - SHA-256 or SHA-384 for signing documents

Migrate from weak hashes

  • Re-hash on login: when a user whose stored value is in the old format authenticates successfully, hash the password you have just verified with the new function and replace the stored value. Users are upgraded without noticing
  • On critical systems, force a password reset so the migration completes at once instead of one login at a time
  • Verify against both formats during the transition, choosing the verifier from the format of the stored value
  • Replace every MD5 and SHA-1 call that produces a hash, not only the one in the finding

Configure appropriate work factors

  • Argon2id: 19 MiB of memory, 2 iterations and 1 degree of parallelism is OWASP's floor; raise memory first
  • scrypt: N=2^17, r=8, p=1 as a floor, raising N as hardware allows
  • bcrypt: cost 10 is the floor; go as high as your authentication latency target allows
  • PBKDF2-HMAC-SHA256: 600,000 iterations or more, where a FIPS-validated implementation is required
  • Measure login time on production-class hardware and tune from there

Test the hash migration

A re-scan confirms the weak hash call is gone. It cannot confirm that anyone can still log in, which is what a transparent-upgrade migration most often breaks. Assert each of these:

  • A user whose stored hash is still in the old format authenticates with the correct password, and their stored value is in the new format when read back afterwards. Only the second half distinguishes "the legacy path works" from "the upgrade fired"
  • The same user's wrong password is rejected both before and after the upgrade, and the stored hash is unchanged by the failed attempt
  • Once a record carries a new-format hash, the old verifier is never consulted for it again - assert on the stored prefix, because a dual-verify path that keeps trying MD5 first leaves the weakness in place for every row. Match the prefix your own stack emits rather than a literal copied from elsewhere: bcrypt is $2b$ from Python's bcrypt and from bcryptjs, $2y$ from PHP's password_hash, and $2a$ from Spring Security and BCrypt.Net, so an assertion pinned to one of them fails against a correct migration on the others. Argon2id is $argon2id$ everywhere
  • A user created after the change never gets an old-format hash, including through registration, admin creation, imports and password reset
  • Hashing the same password twice produces two different stored values. Identical values mean the salt is fixed or absent
  • File integrity checks use SHA-256 or better, and a file whose contents changed by one byte fails verification
  • Login latency under expected concurrency stays inside the target - work factor tuning that is fine on one request can exhaust CPU on a login storm
  • Re-scan with the security scanner to confirm the finding is resolved

Common Vulnerable Patterns

  • MD5, SHA-1 or plain SHA-256 for password storage - all of them fast
  • SHA-1 for digital signatures
  • A hand-rolled salted construction such as md5($salt . $password) - adding a salt does not make a fast hash slow, and it is still this finding

Additional Resources