Understanding Hash Functions: MD5, SHA-256, and When to Use Each
A developer-friendly guide to cryptographic hash functions — how they work, the differences between MD5, SHA-1, SHA-256, and SHA-512, real-world use cases, and why you should never use MD5 for security.
What is a Hash Function?
A hash function takes an input of any size and produces a fixed-size output (called a hash, digest, or checksum). The same input always produces the same output, but even a tiny change in the input produces a completely different hash.
Input: "Hello" → SHA-256: 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
Input: "Hello!" → SHA-256: 334d016f755cd6dc58c53a86e183882f8ec14f52fb05345887c8a5edd42c87b7
Input: "hello" → SHA-256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
Notice how changing a single character (capitalization, adding "!") produces a completely different hash. This is called the avalanche effect.
Properties of Cryptographic Hash Functions
A good cryptographic hash function has these properties:
1. Deterministic
The same input always produces the same output. No randomness involved.
2. Fast to Compute
Generating a hash should be quick (though not too quick — see bcrypt below).
3. Pre-image Resistance
Given a hash, it should be computationally infeasible to find the original input. You can't "reverse" a hash.
4. Second Pre-image Resistance
Given an input, it should be infeasible to find a different input that produces the same hash.
5. Collision Resistance
It should be infeasible to find any two different inputs that produce the same hash.
6. Avalanche Effect
A small change in input should produce a drastically different hash.
Common Hash Algorithms
MD5 (Message Digest 5)
- Output size: 128 bits (32 hex characters)
- Speed: Very fast
- Status: BROKEN for security purposes
- Example:
5d41402abc4b2a76b9719d911017c592
MD5 was designed in 1991 and was widely used for decades. However, collision attacks were demonstrated in 2004, and practical attacks are now trivial. A collision can be generated in seconds on modern hardware.
Still acceptable for:
- File integrity checks (non-security)
- Cache keys
- Deduplication
- Checksums for data transfer
Never use for:
- Password hashing
- Digital signatures
- Certificate verification
- Any security-critical application
SHA-1 (Secure Hash Algorithm 1)
- Output size: 160 bits (40 hex characters)
- Speed: Fast
- Status: DEPRECATED for security
- Example:
aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
SHA-1 was the successor to MD5 and was widely used in SSL certificates and Git. Google demonstrated a practical collision attack (SHAttered) in 2017. Major browsers stopped accepting SHA-1 certificates in 2017.
Still used in:
- Git (for commit hashes — not security-critical in this context)
- Legacy systems being migrated
SHA-256 (SHA-2 family)
- Output size: 256 bits (64 hex characters)
- Speed: Fast
- Status: SECURE — current standard
- Example:
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256 is part of the SHA-2 family designed by the NSA. It's the most widely used secure hash function today, used in:
- TLS/SSL certificates
- Bitcoin mining
- Digital signatures
- HMAC authentication
- File integrity verification
SHA-512
- Output size: 512 bits (128 hex characters)
- Speed: Slightly slower than SHA-256 on 32-bit systems, faster on 64-bit
- Status: SECURE
SHA-512 provides a larger hash output. It's actually faster than SHA-256 on 64-bit processors because it operates on 64-bit words natively.
SHA-3 (Keccak)
- Output sizes: 224, 256, 384, 512 bits
- Speed: Comparable to SHA-2
- Status: SECURE — newest standard
SHA-3 was selected through a public competition (2007-2012) and uses a completely different internal structure (sponge construction) than SHA-2. It provides a backup if SHA-2 is ever broken.
Comparison Table
| Algorithm | Output Size | Speed | Security | Use Case |
|---|---|---|---|---|
| MD5 | 128 bits | Fastest | Broken | Checksums only |
| SHA-1 | 160 bits | Fast | Deprecated | Legacy only |
| SHA-256 | 256 bits | Fast | Secure | General purpose |
| SHA-384 | 384 bits | Fast | Secure | Higher security |
| SHA-512 | 512 bits | Fast | Secure | 64-bit optimized |
| SHA-3-256 | 256 bits | Fast | Secure | Future-proof |
Hashing in Practice
JavaScript (Browser)
async function sha256(message) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
}
// Usage
const hash = await sha256("Hello, World!");
console.log(hash);
Node.js
const crypto = require("crypto");
// SHA-256
const hash = crypto.createHash("sha256")
.update("Hello, World!")
.digest("hex");
// HMAC-SHA256
const hmac = crypto.createHmac("sha256", "secret-key")
.update("Hello, World!")
.digest("hex");
// Hash a file
const fs = require("fs");
const fileHash = crypto.createHash("sha256")
.update(fs.readFileSync("file.txt"))
.digest("hex");
Python
import hashlib
# SHA-256
hash_value = hashlib.sha256(b"Hello, World!").hexdigest()
# MD5 (non-security use only)
md5_value = hashlib.md5(b"Hello, World!").hexdigest()
# Hash a file
with open("file.txt", "rb") as f:
file_hash = hashlib.sha256(f.read()).hexdigest()
# Hash a large file (streaming)
sha256 = hashlib.sha256()
with open("large_file.bin", "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha256.update(chunk)
print(sha256.hexdigest())
Command Line
# SHA-256
echo -n "Hello, World!" | sha256sum
# MD5
echo -n "Hello, World!" | md5sum
# Hash a file
sha256sum file.txt
Password Hashing: A Special Case
Never use MD5, SHA-1, or even SHA-256 directly for password hashing. General-purpose hash functions are too fast, making brute-force attacks feasible.
Instead, use purpose-built password hashing functions:
bcrypt
- Includes a salt automatically
- Has a configurable work factor (cost)
- Deliberately slow to prevent brute-force attacks
const bcrypt = require("bcrypt");
// Hash a password
const hash = await bcrypt.hash("myPassword123", 12);
// Verify a password
const isValid = await bcrypt.compare("myPassword123", hash);
Argon2
- Winner of the Password Hashing Competition (2015)
- Configurable memory, time, and parallelism costs
- Resistant to GPU and ASIC attacks
scrypt
- Memory-hard function
- Used by some cryptocurrency systems
- Good alternative to bcrypt
Why not SHA-256 for passwords?
A modern GPU can compute billions of SHA-256 hashes per second. With a rainbow table or brute-force attack, most passwords can be cracked in minutes. bcrypt with a cost factor of 12 takes about 250ms per hash — making brute-force attacks impractical.
HMAC: Hash-Based Message Authentication
HMAC combines a hash function with a secret key to provide both integrity and authentication:
HMAC(key, message) = Hash((key XOR opad) || Hash((key XOR ipad) || message))
Common uses:
- API authentication (webhook signatures)
- JWT signing (HS256 = HMAC-SHA256)
- Message integrity verification
// Verify a webhook signature
const crypto = require("crypto");
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Generate hashes instantly with our Hash Generator tool.