Hash Functions Explained: MD5, SHA-1, and SHA-256 Compared
This guide has a free tool → Open JWT Decoder
Hash Functions Explained: MD5, SHA-1, and SHA-256 Compared
A hash function takes an input of any size - a single byte, a paragraph of text, or a gigabyte video file - 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 to the input produces a completely different hash.
Input: "hello"
MD5: 5d41402abc4b2a76b9719d911017c592
SHA-1: aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
SHA-256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
Input: "Hello" (capital H - one character changed)
MD5: 8b1a9953c4611296a827abf8c47804d7
SHA-1: f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0
SHA-256: 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969Changing one character - uppercase H versus lowercase h - completely transforms the output. This is called the avalanche effect: a small change in input propagates through the algorithm and flips roughly half of the output bits. This property makes hashes useful for detecting any modification, no matter how subtle.
This guide explains how MD5, SHA-1, and SHA-256 work conceptually, when each is appropriate, where each has failed, and how to use hash functions correctly in real applications.
JWT Decoder
Free online JWT decoder - decode and inspect JSON Web Tokens without sending them to a server
Hash Generator
Free online hash generator - generate MD5, SHA-1, SHA-256 hashes from any input text
AES Encryption Tool
Free online AES encryption tool - encrypt and decrypt text using AES-256 encryption
What Makes a Hash Function Useful
A cryptographically useful hash function must satisfy several properties:
Determinism
The same input always produces the same output. Without this, you cannot use hashes to verify data integrity - if the hash changed on its own, every file would appear tampered with.
Fixed Output Size
Regardless of input size, the output is always the same length. MD5 always produces 128 bits. SHA-256 always produces 256 bits. This predictable output makes hashes easy to store, compare, and transmit.
Pre-image Resistance
Given a hash H, it should be computationally infeasible to find an input M such that hash(M) = H. This is the "one-way" property. You cannot reverse a hash to recover the original input.
Second Pre-image Resistance
Given an input M1 and its hash H, it should be computationally infeasible to find a different input M2 such that hash(M2) = H. Even if an attacker knows your original message, they cannot find a different message with the same hash.
Collision Resistance
It should be computationally infeasible to find any two different inputs M1 and M2 such that hash(M1) = hash(M2). This is the strongest property and the first to break as hash functions age.
The Three Most Common Hash Algorithms
MD5 (Message Digest 5, 1991)
| Property | Value |
|---|---|
| Output size | 128 bits (32 hex characters) |
| Designed | 1991 by Ronald Rivest |
| First collision found | 1996 (theoretical), 2004 (practical) |
| Speed on modern hardware | ~5 GB/s |
| Security status | Broken - do not use for security |
MD5 was designed in 1991 to replace MD4, which was already showing weaknesses. For over a decade, MD5 was the dominant hashing algorithm across the industry - file integrity, digital certificates, password storage, and more all used it.
In 2004, researchers demonstrated the first practical collision: two different files with the same MD5 hash. By 2008, attackers had used MD5 collisions to forge a fraudulent SSL certificate signed by a real certificate authority. By 2012, the Flame malware used MD5 collisions to forge Microsoft software update signatures.
Today, MD5 collisions can be generated in seconds on consumer hardware. The algorithm is completely broken for any purpose that requires collision resistance.
MD5 is still acceptable for:
- Non-security checksums (verifying a file was not corrupted during download when you do not care about tampering)
- Cache keys (the key maps to cached data; collisions would just cause a cache miss)
- Non-security hash table implementations
- Deduplicating content where exact collision resistance is not required
- Generating compact fingerprints of data where performance matters and security does not
MD5 output examples:
MD5("") = d41d8cd98f00b204e9800998ecf8427e
MD5("The quick brown fox") = a2004f37730b9445670a738fa0fc9ee5
MD5("The quick brown fox.") = e4d909c290d0fb1ca068ffaddf22cbd0A single period added to the end flips the entire output - the avalanche effect in action.
SHA-1 (Secure Hash Algorithm 1, 1995)
| Property | Value |
|---|---|
| Output size | 160 bits (40 hex characters) |
| Designed | 1995 by NSA / NIST |
| First theoretical weakness | 2005 |
| Practical collision found | 2017 (SHAttered) |
| Speed on modern hardware | ~2-3 GB/s |
| Security status | Broken - deprecated |
SHA-1 was designed by the NSA and standardized by NIST in 1995 as a stronger successor to MD5. It produces a 160-bit hash, giving it 32 more bits of space than MD5.
In 2017, Google and CWI Amsterdam demonstrated the first practical SHA-1 collision: two different PDF files with exactly the same SHA-1 hash. This attack, called SHAttered, required computing roughly 9.2 × 10^18 SHA-1 compressions - expensive but within reach of nation-state actors and large research organizations.
SHA-1 is now considered insecure for new designs. Most browsers rejected SHA-1 TLS certificates starting in 2017. Certificate authorities stopped issuing SHA-1 certificates in 2016.
Git uses SHA-1 for commit, tree, blob, and tag objects. This is a well-known technical debt. A successful SHA-1 collision in Git would let an attacker create a malicious commit with the same hash as a legitimate one - an extremely serious supply chain attack. Git is in the process of migrating to SHA-256, but the migration is gradual and SHA-1 is still the default as of 2026.
SHA-1 is still seen in:
- Git object names (legacy reason)
- Some older TLS certificate pinning implementations
- Some fingerprinting systems that have not been updated
SHA-1 output examples:
SHA-1("") = da39a3ee5e6b4b0d3255bfef95601890afd80709
SHA-1("hello") = aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
SHA-1("Hello") = f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0SHA-256 (2001)
| Property | Value |
|---|---|
| Output size | 256 bits (64 hex characters) |
| Designed | 2001 by NSA / NIST |
| Known weaknesses | None in the compression function |
| Speed on modern hardware | ~700 MB/s (software), much faster with AES-NI/SHA instructions |
| Security status | Secure - current standard |
SHA-256 is part of the SHA-2 family, designed by the NSA and standardized by NIST in 2001. The "256" in the name refers to the bit length of the output: 64 hexadecimal characters.
SHA-256 has no known practical weaknesses. There are no known collisions, no known pre-image attacks, and no published techniques that bring collision resistance meaningfully below the theoretical brute-force bound of 2^128 operations.
SHA-256 is the right default for any application that needs a hash function today. It is used in:
- TLS certificates (all modern HTTPS connections)
- Bitcoin mining (double SHA-256)
- Code signing (software update signatures on macOS, Windows, Linux packages)
- HMAC authentication (HMAC-SHA256)
- JWT signatures (HS256, RS256)
- Docker image layers
- Content-addressable storage systems
SHA-256 output examples:
SHA-256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("hello world") = b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576f0c16d8000fbed5aComparison Table
| Algorithm | Output Bits | Hex Length | Speed | Collision Attack | Status |
|---|---|---|---|---|---|
| MD5 | 128 | 32 chars | ~5 GB/s | Seconds | Broken |
| SHA-1 | 160 | 40 chars | ~2 GB/s | Hours (SHAttered) | Deprecated |
| SHA-256 | 256 | 64 chars | ~700 MB/s | None known | Secure |
| SHA-384 | 384 | 96 chars | ~500 MB/s | None known | Secure |
| SHA-512 | 512 | 128 chars | ~800 MB/s | None known | Secure |
| SHA3-256 | 256 | 64 chars | ~400 MB/s | None known | Secure (different design) |
| BLAKE3 | Variable | Variable | ~5 GB/s | None known | Secure (modern) |
Note: SHA-512 is often faster than SHA-256 on 64-bit systems because SHA-512 operates on 64-bit words. SHA-256 operates on 32-bit words.
When to Use Each Algorithm
File Integrity Verification
Use SHA-256 for file downloads and backups. When a developer or company publishes a checksum for a downloadable file, they should use SHA-256 (or SHA-512).
# Verify a downloaded file on Linux/macOS
sha256sum ubuntu-24.04.iso
# Compare with the checksum on the download page
# On macOS specifically
shasum -a 256 ubuntu-24.04.iso
# On Windows (PowerShell)
Get-FileHash .\ubuntu-24.04.iso -Algorithm SHA256If you receive a file and its SHA-256 hash, and the hash you compute matches, you can be confident the file was not modified in transit or storage.
Cache Keys
MD5 is acceptable here because security is not the concern - you are just mapping input to a compact, deterministic identifier.
import hashlib
import json
def cache_key(request_params: dict) -> str:
serialized = json.dumps(request_params, sort_keys=True)
return hashlib.md5(serialized.encode()).hexdigest()
key = cache_key({"user_id": 42, "filters": ["active", "verified"]})
# "5d2ae70ebe3c31765f34..." - compact key for Redis or MemcachedIf performance is critical and you are computing many cache keys, MD5's speed advantage over SHA-256 matters. If you prefer not to use broken algorithms anywhere, SHA-256 is equally valid here.
Data Deduplication
import hashlib
def file_hash(filepath: str) -> str:
"""Compute SHA-256 hash of a file for deduplication."""
h = hashlib.sha256()
with open(filepath, "rb") as f:
# Read in 64KB chunks to handle large files
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return h.hexdigest()
# Two files with the same hash are (functionally) the same fileContent-addressable storage systems like Git, IPFS, and many backup tools use hashes to deduplicate: if two pieces of content have the same hash, store them once.
HMAC Authentication
HMAC (Hash-based Message Authentication Code) uses a hash function to authenticate messages. Unlike plain hashing, HMAC requires a secret key - making it suitable for API authentication and webhook verification.
import hmac
import hashlib
def create_signature(payload: bytes, secret: str) -> str:
return hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
def verify_signature(payload: bytes, secret: str, received_sig: str) -> bool:
expected = create_signature(payload, secret)
# Use constant-time comparison to prevent timing attacks
return hmac.compare_digest(expected, received_sig)GitHub uses HMAC-SHA256 to sign webhook payloads. Stripe uses it too. This pattern is the industry standard for webhook authentication.
// Node.js webhook verification
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expectedSig = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
);
}JWT Signatures
JSON Web Tokens use hash functions for signatures. The alg header in a JWT specifies which algorithm is used:
| Algorithm | Description |
|---|---|
HS256 | HMAC with SHA-256 (symmetric - same key signs and verifies) |
HS384 | HMAC with SHA-384 |
HS512 | HMAC with SHA-512 |
RS256 | RSA signature with SHA-256 (asymmetric) |
ES256 | ECDSA with SHA-256 (asymmetric, compact) |
HS256 (HMAC-SHA256) is the most common for server-to-server tokens where both sides share a secret. RS256 is used when you need the verifier to verify without holding the signing key (public/private key pairs).
Use the JWT Decoder to inspect JWT headers and payloads without needing to install any tools locally.
Content-Addressable Storage
# Git uses SHA-1 (migrating to SHA-256) for content addressing
git hash-object somefile.txt
# 83baae61804e65cc73a7201a7252750c76066a30
# A blob object ID in git is: SHA1("blob " + size + "\0" + content)
# Same content always produces the same hash - deduplication is automaticIPFS uses SHA-256 (via multihash) for the same purpose: every piece of content is addressed by its hash, so identical content across millions of nodes is naturally deduplicated and verifiable.
Digital Signatures and Certificates
TLS certificates use SHA-256 for their signature algorithm (SHA256withRSA or SHA256withECDSA). When your browser connects to an HTTPS site, the certificate's SHA-256 signature is verified against the certificate authority's public key.
Modern code signing on every major platform uses SHA-256:
# Verify an RPM package signature on Linux
rpm --checksig package.rpm
# Verify a macOS app signature
codesign --verify --verbose=4 /Applications/MyApp.app
# Verify a Windows binary
Get-AuthenticodeSignature .\installer.exeWhat You Should Never Use General Hash Functions For
Password Storage
This is the most critical mistake in application security. SHA-256 is too fast for password hashing. An attacker with a modern GPU can compute 8-10 billion SHA-256 hashes per second. A typical 8-character lowercase password can be brute-forced in seconds.
The fundamental problem: SHA-256 is designed to be fast. Password hashing needs to be slow on purpose.
Always use a dedicated password hashing algorithm:
| Algorithm | Speed | Salt | Work Factor | Notes |
|---|---|---|---|---|
| bcrypt | ~0.0001 billion/sec | Yes | Configurable (cost factor) | Established standard |
| scrypt | Slower | Yes | Configurable | Memory-hard; resists GPU attacks |
| Argon2id | Slowest | Yes | Configurable (time + memory) | 2015 PHC winner; modern recommendation |
| PBKDF2 | Variable | Yes | Iteration count | FIPS-compliant; weaker than bcrypt at same cost |
# Python: correct password hashing with Argon2
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # 3 iterations
memory_cost=65536, # 64 MB RAM
parallelism=4, # 4 threads
)
# Hash a password (salt is generated automatically)
hashed = ph.hash("user's password")
# Verify
try:
ph.verify(hashed, "user's password") # Returns True
# Check if rehashing is needed (if parameters changed)
if ph.check_needs_rehash(hashed):
hashed = ph.hash("user's password")
except Exception:
print("Invalid password")// Node.js: bcrypt for password hashing
const bcrypt = require('bcrypt');
// Hash (cost factor 12 means ~2^12 iterations)
const hashed = await bcrypt.hash(plainPassword, 12);
// Verify
const isValid = await bcrypt.compare(plainPassword, hashed);Never, under any circumstances, store passwords as MD5 or SHA-256 hashes - even salted. The LinkedIn breach of 2012 exposed 117 million SHA-1 passwords. The RockYou breach used unsalted MD5. The Adobe breach used 3DES encryption (not even a hash). These failures caused real damage to real people.
Generating Random Tokens
A hash of a predictable input is not a secure random value. If you need a password reset token, session ID, or CSRF token, use a cryptographically secure random number generator directly:
import secrets
# Correct: 32 bytes of cryptographic randomness
token = secrets.token_hex(32)
# "e3b0c44298fc1c149afbf4c8996fb924..." - 64 hex chars, 256 bits of entropy
# Wrong: don't hash predictable data to create tokens
import hashlib
bad_token = hashlib.sha256(str(user_id).encode()).hexdigest()
# An attacker who knows user IDs can precompute all tokensImplementing Hash Functions in Different Languages
JavaScript / Node.js
// Node.js: built-in crypto module
const crypto = require('crypto');
// Single-line hash
const md5 = (data) => crypto.createHash('md5').update(data).digest('hex');
const sha1 = (data) => crypto.createHash('sha1').update(data).digest('hex');
const sha256 = (data) => crypto.createHash('sha256').update(data).digest('hex');
console.log(sha256('hello'));
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
// File hashing in Node.js
const fs = require('fs');
function hashFile(filepath) {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256');
const stream = fs.createReadStream(filepath);
stream.on('data', (data) => hash.update(data));
stream.on('end', () => resolve(hash.digest('hex')));
stream.on('error', reject);
});
}// Browser: Web Crypto API (SHA-256 only - no MD5 in browser crypto)
async function sha256Browser(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// Usage
const hash = await sha256Browser('hello world');
console.log(hash);Python
import hashlib
# MD5
md5_hash = hashlib.md5("hello".encode()).hexdigest()
print(md5_hash) # 5d41402abc4b2a76b9719d911017c592
# SHA-1
sha1_hash = hashlib.sha1("hello".encode()).hexdigest()
print(sha1_hash) # aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
# SHA-256
sha256_hash = hashlib.sha256("hello".encode()).hexdigest()
print(sha256_hash) # 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
# SHA-512
sha512_hash = hashlib.sha512("hello".encode()).hexdigest()
# Hashing binary data or a file
with open("document.pdf", "rb") as f:
data = f.read()
file_hash = hashlib.sha256(data).hexdigest()
# All available algorithms
print(hashlib.algorithms_available)
# {'sha256', 'sha3_512', 'sha3_256', 'blake2b', 'md5', 'sha1', ...}Go
package main
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
)
func main() {
data := []byte("hello")
// MD5
md5sum := md5.Sum(data)
fmt.Println("MD5:", hex.EncodeToString(md5sum[:]))
// SHA-1
sha1sum := sha1.Sum(data)
fmt.Println("SHA-1:", hex.EncodeToString(sha1sum[:]))
// SHA-256
sha256sum := sha256.Sum256(data)
fmt.Println("SHA-256:", hex.EncodeToString(sha256sum[:]))
// Hash a file (streaming for large files)
f, _ := os.Open("largefile.bin")
defer f.Close()
h := sha256.New()
io.Copy(h, f)
fmt.Println("File SHA-256:", hex.EncodeToString(h.Sum(nil)))
}Rust
use sha2::{Sha256, Digest};
use md5;
fn main() {
// SHA-256
let mut hasher = Sha256::new();
hasher.update(b"hello");
let result = hasher.finalize();
println!("SHA-256: {:x}", result);
// MD5 (via the md5 crate)
let digest = md5::compute(b"hello");
println!("MD5: {:x}", digest);
}SQL - Computing Hashes in the Database
-- PostgreSQL: built-in hash functions
SELECT MD5('hello');
-- '5d41402abc4b2a76b9719d911017c592'
SELECT encode(sha256('hello'::bytea), 'hex');
-- '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
-- MySQL
SELECT MD5('hello');
SELECT SHA2('hello', 256); -- SHA-256
SELECT SHA2('hello', 384); -- SHA-384
SELECT SHA2('hello', 512); -- SHA-512
-- SQLite (no built-in SHA-256, but available via extensions)
SELECT hex(md5(col)) FROM table; -- Requires md5 extensionHash Collisions: What They Mean in Practice
A collision is two different inputs that produce the same hash output. Collisions have always been theoretically possible (pigeonhole principle: infinite inputs, finite outputs). The question is whether they can be found in practice.
The Birthday Problem
If MD5 produces 128-bit outputs, the "birthday attack" says you need to compute approximately 2^64 hashes before you have a 50% chance of finding any collision. 2^64 is about 18 quintillion operations - feasible for large organizations in 2004, trivial on commodity hardware today.
The birthday attack does not give you control over what collides. More dangerous are "chosen-prefix collisions" where an attacker can craft two documents with arbitrary identical prefixes that share the same hash. Chosen-prefix MD5 collisions have been demonstrated and are what made certificate forgery possible.
What a Collision Actually Enables
A collision lets an attacker create File B that has the same hash as File A. If the system only checks the hash to verify integrity, it will accept File B as identical to File A - even if File B contains malicious code, different contract terms, or different executable behavior.
This is catastrophic for digital signatures and software distribution, which is why MD5 and SHA-1 are prohibited for certificate signing.
Checking Hashes From the Command Line
# Linux: compute SHA-256 hash
sha256sum filename.tar.gz
# Verify against known hash (compare manually or use diff)
echo "expectedhash filename.tar.gz" | sha256sum --check
# macOS
shasum -a 256 filename.tar.gz
# MD5 on Linux
md5sum filename.tar.gz
# MD5 on macOS
md5 filename.tar.gz
# All three at once with OpenSSL
openssl dgst -md5 filename.tar.gz
openssl dgst -sha1 filename.tar.gz
openssl dgst -sha256 filename.tar.gz
# Hash stdin input
echo -n "hello" | sha256sum
# Note: -n prevents echo from adding a newline, which would change the hashUsing ToolBox to Generate and Verify Hashes
The Hash Generator computes MD5, SHA-1, SHA-256, and SHA-512 hashes of any text you enter. All computation happens in your browser using the Web Crypto API - nothing is sent to any server.
Common uses:
- Quick integrity check: Paste a file's expected hash and compare it visually against the one you computed locally
- API debugging: Hash a request payload to verify your HMAC signature computation
- Teaching and documentation: Show how a single-character change produces a completely different hash output
- Format conversion: Convert between hex and base64 representations of the same hash
For more context on when to use hashes versus encryption, see the AES Encryption tool - encryption is reversible (given the key), while hashing is one-way. Choose based on whether you ever need to recover the original value.
If you are working with JWT tokens that embed SHA-256 signatures, the JWT Decoder decodes and displays the header, payload, and signature components. For data integrity workflows involving JSON or CSV files, the JSON Formatter and CSV to JSON converters can help normalize your data to a consistent format before hashing.
Open the Hash Generator - paste any text, instantly see MD5, SHA-1, and SHA-256. No signup, no data sent to servers.
Related Tools
Free, private, no signup required
You might also like
Want higher limits, batch processing, and AI tools?