What Hash Functions Do and Why They Matter
A cryptographic hash function is a deterministic algorithm that takes an input of arbitrary length and produces a fixed-length output called a digest, hash, or checksum. The same input always produces the same output. But crucially, the relationship is one-way: given a hash output, it should be computationally infeasible to reconstruct the original input.
Hash functions underpin an enormous range of security infrastructure: TLS certificate verification, code signing, password storage, digital signatures, blockchain ledgers, data integrity checks, and message authentication codes. They are simultaneously one of the most used and most misused tools in the developer's toolkit.
The four properties that define a good cryptographic hash function are: 1. **Pre-image resistance:** given hash `H`, it is infeasible to find any input `M` such that `hash(M) = H` 2. **Second pre-image resistance:** given input `M1`, it is infeasible to find a different `M2` such that `hash(M1) = hash(M2)` 3. **Collision resistance:** it is infeasible to find any two different inputs `M1` and `M2` such that `hash(M1) = hash(M2)` 4. **Avalanche effect:** a small change to the input (even a single bit) produces a dramatically different output
When these properties are violated — either by mathematical attack or by advances in computing power — the hash function is said to be **broken** for security purposes. MD5 and SHA-1 are broken by this definition. SHA-256 and SHA-512 remain secure as of 2026.
MD5: What It Is, Why It Is Broken, and When It Is Still Acceptable
MD5 (Message Digest Algorithm 5) was designed by Ron Rivest in 1991 and produces a 128-bit (16-byte) hash, typically represented as a 32-character hexadecimal string. For over a decade it was the dominant hash function for security applications. Its demise began in 2004 when researchers demonstrated practical collision attacks — the ability to create two different files with identical MD5 hashes.
By 2008, collision attacks against MD5 were practical enough to create rogue SSL certificates. By 2012, the Flame malware used MD5 collisions to forge Microsoft code-signing certificates. MD5 is **definitively broken** for any security application.
**Where MD5 is still acceptable (non-security contexts):** - **File transfer checksums** where the goal is detecting accidental corruption (not tampering). If you download a file and the MD5 matches the one published by the source, you can be confident the file was not corrupted in transit. An *attacker* could still substitute a malicious file with the same MD5, but for detecting network errors or disk corruption, MD5 is fine. - **Hash-based deduplication** in non-security storage systems where two different files producing the same hash is extremely unlikely in practice (even though it is theoretically possible to force it). - **Non-cryptographic hash tables** in application code where speed matters more than security. - **Legacy system interoperability** where you must produce an MD5 because a downstream system requires it.
In Python: `hashlib.md5(data).hexdigest()`. In Node.js: `crypto.createHash('md5').update(data).digest('hex')`. Always add a comment explaining why MD5 is being used and that it is not for security purposes — future developers will thank you.
SHA-1: The Deprecated Standard You Should Stop Using
SHA-1 (Secure Hash Algorithm 1, FIPS 180-4) was designed by the NSA and published in 1995. It produces a 160-bit (20-byte) digest. For many years it was the workhorse of TLS, code signing, and digital signatures. The SHA-1 collision attack `SHAttered` (2017) produced the first public SHA-1 collision using approximately 6,500 CPU-years of computation. By 2020, `SHA-mbles` reduced the cost of a chosen-prefix collision attack to 45,000 GPU-hours — achievable by a well-funded attacker.
**SHA-1 is officially deprecated across the industry:** - All major browsers removed SHA-1 certificate support in 2017 - Git switched its default hash from SHA-1 to SHA-256 in 2024 (available in `git config extensions.objectFormat sha256`) - NIST formally deprecated SHA-1 for all uses as of December 31, 2030, with recommendation to migrate immediately - Code signing with SHA-1 is rejected by Windows, macOS, and Linux package managers
Despite this, SHA-1 persists in legacy systems. If you encounter SHA-1 in an audit: 1. Identify all uses (TLS, code signing, HMAC, checksums) 2. Replace with SHA-256 in all security contexts immediately 3. For non-security checksums (deduplication, legacy compatibility), migration is lower urgency but still recommended
The migration path is straightforward: SHA-256 is a drop-in algorithmic replacement. The output length changes (40 hex chars → 64 hex chars), so any code that hardcodes the output length will need updating. Database columns storing SHA-1 hashes need widening. These are manageable engineering tasks that should be prioritized.
SHA-256 and SHA-512: The Current Production Standard
SHA-256 and SHA-512 are members of the SHA-2 family, designed by the NSA and standardized by NIST in FIPS 180-4. They remain cryptographically secure as of 2026 with no known practical attacks against their collision resistance or pre-image resistance.
**SHA-256** produces a 256-bit (32-byte) output, represented as 64 hexadecimal characters. It operates on 512-bit (64-byte) blocks and uses 32-bit word operations, which makes it efficient on 32-bit and 64-bit hardware alike. SHA-256 provides **128 bits of collision resistance** (by the birthday bound, `2^128` operations are needed to find a collision).
**SHA-512** produces a 512-bit (64-byte) output, represented as 128 hexadecimal characters. It operates on 1024-bit (128-byte) blocks and uses 64-bit word operations. On 64-bit hardware, SHA-512 is often **faster than SHA-256** for large inputs because it processes more data per round. SHA-512 provides **256 bits of collision resistance**.
**Performance comparison (approximate, 2026 hardware):**
| Algorithm | Output Bits | Speed (MB/s, large input) | Hex Output Length | |-----------|-------------|--------------------------|--------------------| | MD5 | 128 | ~3,000 MB/s | 32 chars | | SHA-1 | 160 | ~1,800 MB/s | 40 chars | | SHA-256 | 256 | ~900 MB/s | 64 chars | | SHA-512 | 512 | ~1,100 MB/s (64-bit CPU) | 128 chars | | SHA-3-256 | 256 | ~500 MB/s | 64 chars |
**When to use SHA-256:** TLS certificates, code signing, HMAC-SHA-256 for API request signing, file integrity verification in security contexts, blockchain applications, JWT signatures (HS256, RS256, ES256).
**When to use SHA-512:** when you need higher security margins (high-value keys, long-term signatures), as the internal hash for PBKDF2 password hashing, applications processing large files on 64-bit servers where the speed advantage is measurable.
Password Hashing: Why You Must Not Use Raw SHA-256
This is the most important section in this article. **Never store passwords hashed with MD5, SHA-1, SHA-256, SHA-512, or any other general-purpose hash function.** These algorithms are designed to be fast, which is exactly the wrong property for password storage.
A modern GPU can compute approximately **10 billion SHA-256 hashes per second**. This means an attacker who obtains your hashed password database can attempt 10 billion password guesses per second per GPU. With a cluster of GPUs, cracking a database of poorly chosen passwords takes minutes.
Password hashing requires an algorithm that is deliberately slow and memory-intensive. The three acceptable options in 2026 are:
**Argon2id (recommended):** Winner of the Password Hashing Competition (2015). Memory-hard, resistant to GPU and ASIC attacks. Parameters: minimum 19 MB memory, 2 iterations, 1 parallelism for interactive logins. Use `argon2-cffi` in Python or `argon2` in Node.js.
**bcrypt:** Widely deployed, battle-tested. Maximum input length of 72 bytes is a limitation for very long passwords. Work factor should be 12+ in 2026. Use `bcrypt` directly in Python (not Passlib), `bcryptjs` in Node.js.
**PBKDF2-SHA-256:** FIPS 140 approved, required for some compliance frameworks (FIPS, FedRAMP). Use at least 600,000 iterations in 2026 (NIST recommendation). Built into Python's `hashlib.pbkdf2_hmac()` and Node.js's `crypto.pbkdf2()`.
```python import bcrypt
# Hash a password password = b"user_password" hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# Verify a password if bcrypt.checkpw(password, hashed): print("Password matches") ```
Always store the full hash output including the algorithm identifier, salt, and parameters. bcrypt and Argon2 encode all of this in the output string, making migration easier when parameters need updating.
HMAC: Adding a Secret Key to Hash Functions
HMAC (Hash-based Message Authentication Code, RFC 2104) is a construction that combines a hash function with a secret key to produce a message authentication code. Unlike a plain hash, an HMAC cannot be forged without knowing the secret key — making it suitable for API request signing, webhook verification, and token generation.
The HMAC formula is: `HMAC(key, message) = H((key ⊕ opad) || H((key ⊕ ipad) || message))` where `H` is the underlying hash function, `opad` and `ipad` are fixed padding constants, and `||` is concatenation. In practice, you never implement this yourself — use your language's built-in HMAC implementation.
**Why HMAC rather than `hash(key + message)`?** Appending a key naively (`hash(key + message)`) is vulnerable to **length-extension attacks** for MD5, SHA-1, and SHA-256. An attacker who knows `hash(key + message)` can compute `hash(key + message + extension)` without knowing the key. HMAC's nested construction closes this vulnerability.
**HMAC in practice:**
Node.js (API request signing): ```javascript const crypto = require('crypto'); const signature = crypto .createHmac('sha256', process.env.API_SECRET) .update(`${method}\n${path}\n${timestamp}\n${body}`) .digest('hex'); // Include as X-Signature header ```
Python: ```python import hmac, hashlib sig = hmac.new( key=secret.encode(), msg=message.encode(), digestmod=hashlib.sha256 ).hexdigest() ```
**Timing-safe comparison:** when verifying an HMAC, always use a timing-safe comparison function (`hmac.compare_digest()` in Python, `crypto.timingSafeEqual()` in Node.js). A standard string comparison leaks timing information that an attacker can exploit to forge signatures.
SHA-3 and BLAKE3: The Next Generation Hash Functions
SHA-2 (SHA-256, SHA-512) is secure, but the cryptographic community has developed alternative designs that offer either a completely different mathematical foundation or significantly higher performance.
**SHA-3** (KECCAK, FIPS 202) was selected by NIST in 2012 after an open competition to provide an alternative to SHA-2 in case SHA-2 vulnerabilities are discovered. SHA-3 uses a fundamentally different construction (sponge construction) compared to SHA-2 (Merkle-Damgård), making them resistant to different classes of attacks. SHA-3-256 produces the same 256-bit output as SHA-256 but with entirely different internals.
When to consider SHA-3: if your threat model includes advances in cryptanalysis against the Merkle-Damgård construction, or if you need a standardized alternative to SHA-2 for compliance reasons. SHA-3 is about 40–60% slower than SHA-256 on software implementations, though hardware support is improving.
**BLAKE3** is the latest in the BLAKE family (a SHA-3 finalist). It is not a NIST standard, but it is extremely fast, secure, and increasingly adopted in performance-sensitive applications. BLAKE3 can hash at speeds exceeding 10 GB/s on modern hardware (using SIMD and multi-threading), making it attractive for file integrity verification of large datasets.
**2026 recommendation matrix:**
| Use Case | Recommended Algorithm | |----------|----------------------| | TLS certificates | SHA-256 | | Password storage | Argon2id or bcrypt | | API request signing | HMAC-SHA-256 | | File integrity (security) | SHA-256 or SHA-512 | | High-speed checksums | BLAKE3 | | Compliance (FIPS) | SHA-256 or SHA-3-256 | | Long-term signatures | SHA-512 | | Legacy compatibility only | MD5 (non-security) |
For day-to-day development, SHA-256 covers 90% of use cases correctly. Upgrade to SHA-512 when you need higher security margins, and use Argon2id unconditionally for passwords.
More in developer tools
View all developer tools guides →