MD5 vs SHA1 vs SHA256: Which Hash Should You Use? (With Live Examples)
Source: Dev.to
Choosing the right algorithm
| Use case | Recommended algorithm(s) | Notes |
|---|---|---|
| Password storage | bcrypt / Argon2 | Never use MD5/SHA‑1/SHA‑256 directly |
| Digital signatures | SHA‑256 | NIST‑approved |
| File integrity | SHA‑256 (or MD5) | MD5 ok only for non‑security uses |
| JWT (HS256) | SHA‑256 | Standard choice |
| Git commits | SHA‑1 → SHA‑256 | Migration in progress |
| TLS certificates | SHA‑256 | SHA‑1 certificates are rejected |
| HMAC | SHA‑256 or SHA‑512 | Both are secure |
How a cryptographic hash works
A hash function takes arbitrary input and produces a fixed‑length output.
import hashlib
text = "Hello, World!"
md5 = hashlib.md5(text.encode()).hexdigest() # 32 hex chars
sha1 = hashlib.sha1(text.encode()).hexdigest() # 40 hex chars
sha256 = hashlib.sha256(text.encode()).hexdigest() # 64 hex chars
sha512 = hashlib.sha512(text.encode()).hexdigest() # 128 hex chars
print(sha256)
# 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3
Key properties
- Deterministic – same input always yields the same output.
- One‑way – infeasible to reverse the hash.
- Avalanche effect – a single‑bit change completely changes the output.
- Collision resistant – hard to find two distinct inputs with the same hash.
MD5
Produces a 128‑bit (32‑hex‑char) hash.
- Status: Cryptographically broken. Use only for non‑security checksums.
- Historical attacks:
- 2004 – collision attacks demonstrated.
- 2008 – rogue CA certificates created using MD5 collisions.
- 2012 – Flame malware forged Windows Update signatures.
// Node.js
const crypto = require('crypto');
const hash = crypto.createHash('md5').update('Hello').digest('hex');
console.log(hash); // 8b1a9953c4611296a827abf8c47804d7
Safe uses
- Non‑security file deduplication.
- Caching keys where collisions are acceptable.
- Legacy system compatibility.
Unsafe uses
- Password hashing.
- Digital signatures.
- SSL/TLS certificates.
- Any security‑critical context.
SHA‑1
Produces a 160‑bit (40‑hex‑char) hash.
- Status: Deprecated for new code; collisions are feasible.
- Notable attack: 2017 “SHAttered” collision demonstrated two different PDFs with identical SHA‑1 hashes.
# Two different PDFs with the same SHA‑1 hash
sha1sum shattered-1.pdf shattered-2.pdf
# 38762cf7f55934b34d179ae6a4c80cadccbb7f0a shattered-1.pdf
# 38762cf7f55934b34d179ae6a4c80cadccbb7f0a shattered-2.pdf
- Git still uses SHA‑1 for commit IDs (migration to SHA‑256 is underway).
- Browsers reject TLS certificates signed with SHA‑1.
Recommendation: Avoid SHA‑1 in new applications; migrate existing uses where possible.
SHA‑256 (part of the SHA‑2 family)
Produces a 256‑bit (64‑hex‑char) hash.
- Status: Current standard; widely trusted.
- Common uses:
- Bitcoin blockchain.
- TLS 1.3.
- JWT signatures (HS256, RS256, ES256).
- Code‑signing certificates.
- Password managers.
// Node.js
const crypto = require('crypto');
const hash = crypto.createHash('sha256').update('Hello').digest('hex');
console.log(hash); // 185f8db32921bd46d35cc3c66b9a7b8f3a2bc37f9f0e87c
// Browser (Web Crypto API)
async function sha256(text) {
const data = new TextEncoder().encode(text);
const buf = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
sha256('Hello').then(console.log);
import hashlib
print(hashlib.sha256(b'Hello').hexdigest())
HMAC (Hash‑based Message Authentication Code)
Combines a secret key with a hash function to authenticate messages.
import hmac, hashlib
secret = b'my-secret-key'
message = b'user_id=123&amount=50'
sig = hmac.new(secret, message, hashlib.sha256).hexdigest()
print(sig) # Verify that the message wasn't tampered with
Typical uses
- JWT signatures (HS256 = HMAC‑SHA256).
- Webhook payload verification (GitHub, Stripe).
- API request signing (AWS Signature V4).
Password hashing: why plain SHA‑256 is wrong
# WRONG — never do this
import hashlib
password_hash = hashlib.sha256(password.encode()).hexdigest()
- Too fast – attackers can try billions of guesses per second.
- No salt – vulnerable to rainbow‑table attacks.
- Deterministic – identical passwords produce identical hashes, leaking duplicates.
Correct approach
# RIGHT — use bcrypt (or Argon2/scrypt)
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
Properties of proper password hashers
- Slow – configurable cost factor.
- Salted – unique per password.
- Memory‑hard – Argon2 and scrypt resist GPU/ASIC attacks.
Quick language snippets
Node.js (crypto module)
const { createHash, createHmac } = require('crypto');
createHash('sha256').update('text').digest('hex');
createHmac('sha256', 'key').update('text').digest('hex');
Python
import hashlib, hmac
hashlib.sha256(b'text').hexdigest()
hmac.new(b'key', b'text', hashlib.sha256).hexdigest()
Go
import "crypto/sha256"
import "fmt"
func main() {
h := sha256.Sum256([]byte("text"))
fmt.Printf("%x\n", h)
}
Shell
echo -n "text" | sha256sum
echo -n "text" | md5sum
API example: programmatic hashing
curl -X POST https://api.aiforeverthing.com/api/hash/generate \
-H "Content-Type: application/json" \
-d '{"text": "hello world", "algorithm": "sha256"}'
Summary cheat‑sheet
| Algorithm | Typical use case | Security stance |
|---|---|---|
| MD5 | Non‑security checksums, legacy deduplication | Fast, broken – use only for non‑security |
| SHA‑1 | Legacy systems (e.g., old Git repos) | Deprecated – avoid in new code |
| SHA‑256 | General purpose hashing, signatures, JWT, TLS | Current standard – default choice |
| SHA‑512 | Maximum security, often faster on 64‑bit CPUs | Strong – use when extra margin is needed |
| bcrypt / Argon2 / scrypt | Password storage | Always use a dedicated password hasher |
| HMAC‑SHA256 (or SHA‑512) | Message authentication, API signing, JWT | Secure for authenticated contexts |
Choose the algorithm that matches the security requirements of your application; never use a broken hash (MD5, SHA‑1) for anything security‑critical.