Use of Weak Hash

Description

Use of Weak Hash is a vulnerability that occurs when a product uses a hashing algorithm that does not meet security requirements, allowing attackers to determine original input values, find hash collisions, or perform birthday attacks more efficiently than brute force methods would allow. A cryptographic hash function must be computationally infeasible to invert (one-way), deterministically produce the same output for identical inputs, and resist preimage, second preimage, and birthday attacks. Weak hash functions including MD5, SHA-1, and unsalted SHA-2 variants fail to meet these requirements for security-critical applications, particularly password storage where rainbow table attacks are highly effective.

Risk

Weak hash functions expose applications to multiple attack vectors. Rainbow tables containing precomputed hashes for common passwords can instantly reverse MD5 or SHA-1 password hashes without salting. Collision attacks on MD5 and SHA-1 enable certificate forgery, allowing attackers to create malicious certificates with the same hash as legitimate ones. Fast hash functions like MD5, SHA-1, and even SHA-256 enable rapid brute force attacks using GPUs or specialized hardware. For password storage, modern GPUs can compute billions of MD5 hashes per second. The risk extends beyond password cracking: integrity verification using weak hashes can be bypassed by crafting colliding inputs, digital signatures become forgeable, and content-addressed storage systems become vulnerable to manipulation. These attacks are not theoretical - they have been demonstrated practically and exploited in the wild.

Solution

Use cryptographic hash functions appropriate for the specific use case. For password storage, use adaptive hash functions with configurable work factors: bcrypt, scrypt, or Argon2id. These functions incorporate salts automatically and allow tuning computational cost to remain secure as hardware improves. For general integrity verification and digital signatures, use SHA-256 or SHA-3. Never use MD5, SHA-1, or unsalted hashes for security purposes. When upgrading from weak hashes, implement gradual migration that re-hashes passwords on successful login. For HMAC construction, even MD5 and SHA-1 are acceptable due to HMAC's security properties, but SHA-256 is preferred. Configure appropriate work factors considering both security and denial-of-service risks from expensive hash operations.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Attackers can bypass authentication by recovering passwords from weak hashes using rainbow tables, brute force attacks, or precomputed dictionaries.
IntegrityScope: Integrity

Collision attacks on weak hashes enable attackers to forge data with matching hashes, bypassing integrity verification and potentially allowing code or certificate substitution.
Non-RepudiationScope: Non-Repudiation

Digital signatures using weak hashes can be forged through collision attacks, undermining the ability to prove authorship or detect tampering.

Example Code

Vulnerable Code (Python/Java)

The following examples demonstrate use of weak hash functions:

# Vulnerable: Using weak hash functions
import hashlib

# Vulnerable: MD5 for password storage
def vulnerable_store_password(password):
    # Vulnerable: MD5 is cryptographically broken
    # Rainbow tables can crack these instantly
    return hashlib.md5(password.encode()).hexdigest()

def vulnerable_verify_password(password, stored_hash):
    return hashlib.md5(password.encode()).hexdigest() == stored_hash

# Vulnerable: SHA-1 for integrity
def vulnerable_compute_checksum(data):
    # Vulnerable: SHA-1 has practical collision attacks
    return hashlib.sha1(data).hexdigest()

# Vulnerable: Unsalted SHA-256 for passwords
def vulnerable_sha256_password(password):
    # Vulnerable: Without salt, identical passwords produce identical hashes
    # Rainbow tables exist for SHA-256 passwords too
    return hashlib.sha256(password.encode()).hexdigest()

# Vulnerable: CRC32 for integrity (not cryptographic at all)
import zlib

def vulnerable_crc_checksum(data):
    # Vulnerable: CRC32 is NOT a cryptographic hash
    # Trivial to find collisions
    return hex(zlib.crc32(data) & 0xffffffff)

# Vulnerable: Simple salted hash with fast algorithm
def vulnerable_salted_md5(password, salt):
    # Vulnerable: MD5 is too fast, even with salt
    # GPUs can try billions per second
    return hashlib.md5((salt + password).encode()).hexdigest()

# Vulnerable: Single iteration of any hash
def vulnerable_single_hash(password):
    # Vulnerable: Single iteration is too fast
    return hashlib.sha512(password.encode()).hexdigest()
// Vulnerable: Using weak hash functions in Java
import java.security.*;
import java.util.*;

public class VulnerableHashing {

    // Vulnerable: MD5 for password storage
    public String vulnerableMd5Password(String password) throws Exception {
        // Vulnerable: MD5 is broken
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] hash = md.digest(password.getBytes("UTF-8"));
        return bytesToHex(hash);
    }

    // Vulnerable: SHA-1 for integrity
    public String vulnerableSha1Checksum(byte[] data) throws Exception {
        // Vulnerable: SHA-1 has collision attacks
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        return bytesToHex(md.digest(data));
    }

    // Vulnerable: Unsalted SHA-256
    public String vulnerableSha256(String password) throws Exception {
        // Vulnerable: No salt means rainbow tables work
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        return bytesToHex(md.digest(password.getBytes()));
    }

    // Vulnerable: Custom hash combination
    public String vulnerableCustomHash(String password) throws Exception {
        // Vulnerable: Combining weak hashes doesn't make them stronger
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");

        byte[] md5Hash = md5.digest(password.getBytes());
        byte[] sha1Hash = sha1.digest(password.getBytes());

        // XORing weak hashes together
        byte[] combined = new byte[16];
        for (int i = 0; i < 16; i++) {
            combined[i] = (byte)(md5Hash[i] ^ sha1Hash[i]);
        }
        return bytesToHex(combined);
    }

    // Vulnerable: Using hash for encryption
    public byte[] vulnerableHashEncrypt(byte[] data, String key) throws Exception {
        // Vulnerable: Hash functions are not encryption!
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] keyHash = md.digest(key.getBytes());

        // XOR with hash - this is NOT secure encryption
        byte[] result = new byte[data.length];
        for (int i = 0; i < data.length; i++) {
            result[i] = (byte)(data[i] ^ keyHash[i % keyHash.length]);
        }
        return result;
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
// Vulnerable: Using weak hash functions in C
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <string.h>
#include <stdio.h>

// Vulnerable: MD5 password hash
void vulnerable_md5_password(const char *password, char *output) {
    unsigned char hash[MD5_DIGEST_LENGTH];

    // Vulnerable: MD5 is broken
    MD5((unsigned char*)password, strlen(password), hash);

    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
        sprintf(output + (i * 2), "%02x", hash[i]);
    }
}

// Vulnerable: SHA-1 integrity check
void vulnerable_sha1_file(const char *filename, unsigned char *output) {
    // Vulnerable: SHA-1 collision attacks are practical
    FILE *f = fopen(filename, "rb");
    SHA_CTX ctx;
    SHA1_Init(&ctx);

    unsigned char buffer[4096];
    size_t bytes;
    while ((bytes = fread(buffer, 1, sizeof(buffer), f)) > 0) {
        SHA1_Update(&ctx, buffer, bytes);
    }

    SHA1_Final(output, &ctx);
    fclose(f);
}

// Vulnerable: Simple salted hash
void vulnerable_salted_hash(const char *password, const char *salt,
                            char *output) {
    char combined[256];
    snprintf(combined, sizeof(combined), "%s%s", salt, password);

    // Vulnerable: Single SHA-256 is too fast
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((unsigned char*)combined, strlen(combined), hash);

    for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
        sprintf(output + (i * 2), "%02x", hash[i]);
    }
}

Fixed Code (Python/Java)

# Fixed: Using secure hash functions
import bcrypt
import hashlib
import hmac
import secrets

# Fixed: bcrypt for password storage
def secure_store_password(password):
    # Fixed: bcrypt with automatic salt and work factor
    return bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))

def secure_verify_password(password, stored_hash):
    # Fixed: Constant-time comparison built into bcrypt
    return bcrypt.checkpw(password.encode(), stored_hash)

# Fixed: Argon2 for password storage (preferred)
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

def secure_argon2_password(password):
    # Fixed: Argon2id with memory-hard properties
    ph = PasswordHasher(
        time_cost=3,
        memory_cost=65536,  # 64 MB
        parallelism=4
    )
    return ph.hash(password)

def secure_verify_argon2(password, hash):
    ph = PasswordHasher()
    try:
        ph.verify(hash, password)
        return True
    except VerifyMismatchError:
        return False

# Fixed: SHA-256 for integrity (where collision resistance matters less)
def secure_compute_checksum(data):
    # Fixed: SHA-256 for general integrity
    return hashlib.sha256(data).hexdigest()

# Fixed: SHA-3 for highest security
def secure_sha3_hash(data):
    # Fixed: SHA-3 has different construction than SHA-2
    return hashlib.sha3_256(data).hexdigest()

# Fixed: HMAC for keyed hashing
def secure_hmac(key, data):
    # Fixed: HMAC provides authentication
    return hmac.new(key, data, hashlib.sha256).hexdigest()

# Fixed: Constant-time comparison
def secure_compare_hashes(hash1, hash2):
    # Fixed: Prevent timing attacks
    return hmac.compare_digest(hash1, hash2)

# Fixed: PBKDF2 as alternative to bcrypt
def secure_pbkdf2_password(password, salt=None):
    if salt is None:
        salt = secrets.token_bytes(16)

    # Fixed: High iteration count
    dk = hashlib.pbkdf2_hmac(
        'sha256',
        password.encode(),
        salt,
        600000  # OWASP recommended minimum
    )

    return salt + dk
// Fixed: Using secure hash functions in Java
import org.mindrot.jbcrypt.BCrypt;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.*;

public class SecureHashing {

    // Fixed: bcrypt for password storage
    public String secureHashPassword(String password) {
        // Fixed: bcrypt with automatic salt and work factor
        return BCrypt.hashpw(password, BCrypt.gensalt(12));
    }

    public boolean secureVerifyPassword(String password, String hash) {
        // Fixed: Constant-time comparison
        return BCrypt.checkpw(password, hash);
    }

    // Fixed: PBKDF2 for password-based key derivation
    public byte[] securePbkdf2(String password, byte[] salt) throws Exception {
        // Fixed: High iteration count
        SecretKeyFactory factory = SecretKeyFactory.getInstance(
            "PBKDF2WithHmacSHA256");

        KeySpec spec = new PBEKeySpec(
            password.toCharArray(),
            salt,
            600000,  // High iterations
            256
        );

        return factory.generateSecret(spec).getEncoded();
    }

    // Fixed: SHA-256 for integrity
    public byte[] secureChecksum(byte[] data) throws Exception {
        // Fixed: SHA-256 for general integrity
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        return md.digest(data);
    }

    // Fixed: SHA-3 for highest security
    public byte[] secureSha3(byte[] data) throws Exception {
        // Fixed: SHA3-256
        MessageDigest md = MessageDigest.getInstance("SHA3-256");
        return md.digest(data);
    }

    // Fixed: HMAC for keyed hashing
    public byte[] secureHmac(byte[] key, byte[] data) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(key, "HmacSHA256");
        mac.init(keySpec);
        return mac.doFinal(data);
    }

    // Fixed: Constant-time hash comparison
    public boolean secureCompare(byte[] hash1, byte[] hash2) {
        // Fixed: MessageDigest.isEqual is constant-time
        return MessageDigest.isEqual(hash1, hash2);
    }

    // Fixed: Generate secure salt
    public byte[] generateSalt() {
        byte[] salt = new byte[16];
        SecureRandom.getInstanceStrong().nextBytes(salt);
        return salt;
    }
}
// Fixed: Using secure hash functions in C
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <argon2.h>
#include <string.h>

// Fixed: Argon2id for password hashing
int secure_hash_password(const char *password, char *output, size_t output_len) {
    uint8_t salt[16];

    // Generate random salt
    if (RAND_bytes(salt, sizeof(salt)) != 1) {
        return -1;
    }

    // Fixed: Argon2id with secure parameters
    int result = argon2id_hash_encoded(
        3,              // time_cost
        65536,          // memory_cost (64 MB)
        4,              // parallelism
        password, strlen(password),
        salt, sizeof(salt),
        32,             // hash length
        output, output_len
    );

    return result == ARGON2_OK ? 0 : -1;
}

int secure_verify_password(const char *password, const char *hash) {
    // Fixed: Argon2 verification with constant-time comparison
    return argon2id_verify(hash, password, strlen(password)) == ARGON2_OK;
}

// Fixed: SHA-256 for integrity
int secure_sha256(const unsigned char *data, size_t len,
                  unsigned char *output) {
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();

    // Fixed: SHA-256 using EVP interface
    EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
    EVP_DigestUpdate(ctx, data, len);

    unsigned int hash_len;
    EVP_DigestFinal_ex(ctx, output, &hash_len);

    EVP_MD_CTX_free(ctx);
    return hash_len;
}

// Fixed: SHA-3 for highest security
int secure_sha3_256(const unsigned char *data, size_t len,
                    unsigned char *output) {
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();

    // Fixed: SHA3-256
    EVP_DigestInit_ex(ctx, EVP_sha3_256(), NULL);
    EVP_DigestUpdate(ctx, data, len);

    unsigned int hash_len;
    EVP_DigestFinal_ex(ctx, output, &hash_len);

    EVP_MD_CTX_free(ctx);
    return hash_len;
}

// Fixed: Constant-time comparison
int secure_compare(const unsigned char *a, const unsigned char *b, size_t len) {
    // Fixed: CRYPTO_memcmp is constant-time
    return CRYPTO_memcmp(a, b, len) == 0;
}

// Fixed: PBKDF2 for key derivation
int secure_pbkdf2(const char *password, const unsigned char *salt,
                  size_t salt_len, unsigned char *key, size_t key_len) {
    // Fixed: High iteration count
    return PKCS5_PBKDF2_HMAC(
        password, strlen(password),
        salt, salt_len,
        600000,  // High iterations
        EVP_sha256(),
        key_len, key
    );
}

The fix uses bcrypt, Argon2, or PBKDF2 for passwords, and SHA-256/SHA-3 for integrity verification.


Exploited in the Wild

LinkedIn Password Breach (2012)

6.5 million LinkedIn password hashes were leaked, stored as unsalted SHA-1. Most were cracked within days using rainbow tables and GPU-based attacks.

Flame Malware MD5 Collision (2012)

The Flame malware used an MD5 collision attack to forge Windows Update certificates, allowing malware distribution that appeared to come from Microsoft.


Tools to Test/Exploit

  • hashcat — GPU-accelerated password hash cracker supporting many weak algorithms.

  • John the Ripper — Classic password cracker with extensive format support.

  • Rainbow Crack — Rainbow table generation and lookup.


CVE Examples


References

  1. MITRE Corporation. "CWE-328: Use of Weak Hash." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/328.html

  2. OWASP Foundation. "Password Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html

  3. NIST. "Digital Identity Guidelines." SP 800-63B. https://pages.nist.gov/800-63-3/sp800-63b.html