Hashing & Checksums cheat sheet
A scannable Hashing & Checksums reference: 26 short snippets across 13 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Hash functions: MD5, SHA-1, SHA-256 | A cryptographic hash function takes input of any size and returns a fixed-length fingerprint (the digest). Good | lesson |
| Checksums & verifying files | When you download software, a flipped bit or a tampered mirror can corrupt or poison the file. A published checksum | lesson |
| Non-cryptographic hashes: CRC32, FNV and xxHash | A non-cryptographic hash is built for speed and distribution, not for resistance. It is trivial to construct inputs | lesson |
| Hashes inside data structures | A hash table turns the hash into a bucket index with a modulo or a bitmask. Two keys can land in the same bucket, so | lesson |
| HMAC and keyed hashing | Anyone can compute a SHA-256 of anything. Appending the digest to a message proves only that the message was not | lesson |
| Password hashing: bcrypt, scrypt and Argon2 | A GPU computes billions of SHA-256 hashes per second. A password has only tens of bits of real entropy, so a fast hash | lesson |
| Salts, peppers and work factors | A salt is a unique random value per stored password. Its job is to make precomputation useless and to stop two users | lesson |
| Content addressing: Git, IPFS and deduplication | In content-addressed storage the key is a hash of the value. Two identical files have the same key automatically | lesson |
| Cache keys, ETags and shard keys | A cache key must include every input that changes the output. Miss one — a locale, a feature flag, the requesting | lesson |
| Collisions, birthday attacks and length extension | The birthday bound says a collision becomes likely after roughly the square root of the output space. For a 128-bit | lesson |
| SHA-3, BLAKE2 and BLAKE3 | BLAKE3 splits the input into chunks, hashes them in parallel and combines them with a Merkle tree. That makes it the | lesson |
| Digital signatures and certificates | Public-key operations are slow and only work on small inputs, so a signature covers a hash of the message. That is why | lesson |
| Migrating off MD5 and SHA-1 safely | A big-bang switch breaks every stored value at once. The safe pattern is to store both digests, verify against either | lesson |
Quick snippets
Hash functions: MD5, SHA-1, SHA-256
What a hash function does
sha256("hello") =
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
Hashing passwords is different
# DO NOT: hash = sha256(password) # fast, unsalted
# DO: a slow, salted KDF
import hashlib, secrets
pwd = b'correct horse battery staple'
salt = secrets.token_bytes(16)
dk = hashlib.pbkdf2_hmac('sha256', pwd, salt, 200000)
# store salt + dk; verify by recomputingFull lesson: Hash functions: MD5, SHA-1, SHA-256 →
Checksums & verifying files
Why verify a file
# Linux / macOS
sha256sum downloaded.iso
# Windows (PowerShell)
Get-FileHash downloaded.iso -Algorithm SHA256Full lesson: Checksums & verifying files →
Non-cryptographic hashes: CRC32, FNV and xxHash
CRC is an error detector, not a security control
# CRC32 is not a checksum for downloads: it is far too weak
python -c "import zlib,sys; print('%08x' % zlib.crc32(open('file.bin','rb').read()))"
# for downloads use SHA-256, not CRC32
Choosing one for a hash table
# FNV-1a 64-bit, written out so the mixing is visible
def fnv1a_64(data: bytes) -> int:
h = 0xCBF29CE484222325
for b in data:
h ^= b
h = (h * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF
return h
for key in (b"user:1", b"user:2", b"user:3"):
print(key, fnv1a_64(key))Full lesson: Non-cryptographic hashes: CRC32, FNV and xxHash →
Hashes inside data structures
Consistent hashing
# rendezvous hashing: no ring, no virtual nodes
import hashlib
def pick(key, nodes):
return max(nodes, key=lambda n: int(
hashlib.sha256(f"{key}|{n}".encode()).hexdigest()[:16], 16))
nodes = ["cache-a", "cache-b", "cache-c"]
print(pick("user:1", nodes))Full lesson: Hashes inside data structures →
HMAC and keyed hashing
A hash proves integrity only against accident
import hashlib
message = b"transfer=100&to=alice"
naive = hashlib.sha256(message).hexdigest()
# the attacker edits the message and recomputes — nothing detects it
forged = b"transfer=9999&to=alice"
attacker_digest = hashlib.sha256(forged).hexdigest() # valid-looking, worthless
# even the secret-prefix construction is broken: length extension
broken = hashlib.sha256(b"secret" + message).hexdigest()
The HMAC construction
# compute an HMAC on the command line
printf 'transfer=100&to=alice' | openssl dgst -sha256 -hmac "$(cat secret.key)"
# HMAC-SHA256(stdin)= 4f2a...Full lesson: HMAC and keyed hashing →
Password hashing: bcrypt, scrypt and Argon2
Argon2id in practice
# bcrypt: still fine, but mind the 72-byte limit
import bcrypt
h = bcrypt.hashpw(b"correct horse battery staple", bcrypt.gensalt(rounds=12))
bcrypt.checkpw(b"correct horse battery staple", h) # True
# pre-hash long inputs, but be careful: base64 avoids null bytes in bcrypt
import base64, hashlib
long = base64.b64encode(hashlib.sha256(b"a" * 200).digest())Full lesson: Password hashing: bcrypt, scrypt and Argon2 →
Salts, peppers and work factors
A salt is not a secret
import hashlib, secrets
def hash_password(password: str) -> str:
salt = secrets.token_bytes(16) # cryptographically random
dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 600_000)
return f"pbkdf2_sha256$600000${salt.hex()}${dk.hex()}"
def verify(stored: str, password: str) -> bool:
algo, iters, salt_hex, dk_hex = stored.split("$")
dk = hashlib.pbkdf2_hmac(algo_to_hash(algo), password.encode(),
bytes.fromhex(salt_hex), int(iters))
return secrets.compare_digest(dk.hex(), dk_hex)
Choosing and raising a work factor
import time, hashlib, secrets
def measure(iterations):
salt = secrets.token_bytes(16)
t0 = time.perf_counter()
hashlib.pbkdf2_hmac("sha256", b"password", salt, iterations)
return time.perf_counter() - t0
for n in (100_000, 300_000, 600_000, 1_200_000):
print(n, f"{measure(n) * 1000:.1f} ms")
Migrating a legacy store
def verify_and_upgrade(user, password):
scheme = user.hash_scheme # "md5", "pbkdf2", "argon2id"
if scheme == "md5":
if not legacy_md5_verify(user.password_hash, password):
return False
user.password_hash = argon2_hash(password) # upgrade in place
user.hash_scheme = "argon2id"
db.save(user)
return True
return argon2_verify(user.password_hash, password)Full lesson: Salts, peppers and work factors →
Content addressing: Git, IPFS and deduplication
Name the content, not the location
import hashlib
def digest(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
a = b"shared header" + b"body A"
b = b"shared header" + b"body B"
print(digest(a))
print(digest(b))
# identical content, identical key, stored once
assert digest(b"same bytes") == digest(b"same bytes")
Git is a hash-addressed object store
# the object id is the hash of "blob <len>\0" plus the content
printf 'hello\n' | git hash-object --stdin
# ce013625030ba8dba906f756967f9e9ca394464a
git cat-file -t ce013625030ba8dba906f756967f9e9ca394464a # blob
git cat-file -p ce013625030ba8dba906f756967f9e9ca394464a # hello
# a tree references blobs by hash; a commit references a tree and parents
git cat-file -p HEAD | head -4Full lesson: Content addressing: Git, IPFS and deduplication →
Cache keys, ETags and shard keys
ETags: weak and strong
GET /report.pdf HTTP/1.1
HTTP/1.1 200 OK
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: public, max-age=300
GET /report.pdf HTTP/1.1
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
HTTP/1.1 304 Not Modified
ETags: weak and strong
// server: derive an ETag cheaply from the stored version
const etag = '"' + crypto.createHash("sha256")
.update(String(row.updatedAt.getTime()) + ":" + row.revision)
.digest("hex").slice(0, 32) + '"';
if (req.headers["if-none-match"] === etag) {
return res.status(304).end();
}
res.set("ETag", etag).json(row);
Shard keys and hot partitions
import hashlib
def partition(tenant: str, record_id: str, buckets: int = 64) -> str:
"""Keep a tenant's records on a small set of buckets, spread across many."""
slot = int(hashlib.sha256(f"{tenant}:{record_id}".encode()).hexdigest(), 16) % buckets
return f"{tenant}-{slot:02d}"Full lesson: Cache keys, ETags and shard keys →
Collisions, birthday attacks and length extension
Collisions arrive sooner than intuition suggests
# how many values before a 50 percent collision chance?
import math
def birthday_bound(bits):
return math.isqrt(2 ** bits) if bits % 2 == 0 else 2 ** ((bits + 1) // 2)
for bits in (64, 128, 160, 256):
print(bits, f"~2^{birthday_bound(bits).bit_length() - 1}")
Collision and second-preimage attacks
# length extension in outline: SHA-256 is vulnerable if used as SHA256(secret || msg)
# an attacker who knows len(secret) and the digest can continue the state
def vulnerable_mac(key, msg):
return hashlib.sha256(key + msg).hexdigest() # never do this
# HMAC closes the hole because the key is used twice, outside the message path
def safe_mac(key, msg):
return hmac.new(key, msg, hashlib.sha256).hexdigest()
# SHA-3 and BLAKE2 are not Merkle-Damgard, so extension does not apply
def modern_mac(key, msg):
return hashlib.blake2b(msg, key=key[:64], digest_size=32).hexdigest()
What to do about it
# detect retired algorithms in a codebase
grep -rEn "\b(md5|sha1|SHA-1|MD5)\b" --include="*.py" --include="*.js" --include="*.java" .
grep -rn "has_algorithm\|use_algorithm" . | grep -i md5Full lesson: Collisions, birthday attacks and length extension →
SHA-3, BLAKE2 and BLAKE3
Two constructions, three families
# SHA-3 and SHAKE from the command line
printf 'hello' | openssl dgst -sha3-256
printf 'hello' | openssl dgst -shake256 -xoflen 64
# BLAKE2 ships with many standard tools
printf 'hello' | b2sum -l 256
printf 'hello' | openssl dgst -blake2b512Full lesson: SHA-3, BLAKE2 and BLAKE3 →
Digital signatures and certificates
You sign a digest, not a document
sign: message -> hash -> sign(hash, private key) -> signature
verify: message -> hash -+
+-> verify(signature, public key) -> valid?
signature ---------+
You sign a digest, not a document
# generate a key pair and sign a digest
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out key.pem
openssl pkey -in key.pem -pubout -out pub.pem
openssl dgst -sha256 -sign key.pem -out file.sig file.bin
openssl dgst -sha256 -verify pub.pem -signature file.sig file.bin
# Verified OK
Chains, code signing and reproducible builds
# verify a release: signature, then digest, then the artifact
gpg --verify release.tar.gz.sig release.tar.gz
sha256sum -c SHA256SUMS
# inspect a certificate chain
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -datesFull lesson: Digital signatures and certificates →
Migrating off MD5 and SHA-1 safely
Know what you are replacing
# find the usage sites before changing anything
grep -rIn --exclude-dir=.git -E "\b(MD5|SHA1|sha-1|sha1|md5)\b" . | head -50
grep -rIn "hashlib.new\|MessageDigest.getInstance\|crypto.createHash" . | head -50
# classify by datastore too: hashes already written to disk or a database
rg -n "md5|sha1" db/migrations/ config/ 2>/dev/null
Rollback and verification
def audit_sample(limit=1000):
rows = db.sample_records(limit)
mismatches = [r.id for r in rows if r.sha256 and
r.sha256 != hashlib.sha256(r.data).hexdigest()]
stats.gauge("checksum.mismatches", len(mismatches))
if mismatches:
raise RuntimeError(f"{len(mismatches)} checksum mismatches: {mismatches[:5]}")Full lesson: Migrating off MD5 and SHA-1 safely →
FAQ
Is this Hashing & Checksums cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
Algorithms Data Structures Computer Networks Operating Systems Character Encodings Data Formats
Last refreshed 2026-09-27.