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

TopicWhat it covers
Hash functions: MD5, SHA-1, SHA-256A cryptographic hash function takes input of any size and returns a fixed-length fingerprint (the digest). Goodlesson
Checksums & verifying filesWhen you download software, a flipped bit or a tampered mirror can corrupt or poison the file. A published checksumlesson
Non-cryptographic hashes: CRC32, FNV and xxHashA non-cryptographic hash is built for speed and distribution, not for resistance. It is trivial to construct inputslesson
Hashes inside data structuresA hash table turns the hash into a bucket index with a modulo or a bitmask. Two keys can land in the same bucket, solesson
HMAC and keyed hashingAnyone can compute a SHA-256 of anything. Appending the digest to a message proves only that the message was notlesson
Password hashing: bcrypt, scrypt and Argon2A GPU computes billions of SHA-256 hashes per second. A password has only tens of bits of real entropy, so a fast hashlesson
Salts, peppers and work factorsA salt is a unique random value per stored password. Its job is to make precomputation useless and to stop two userslesson
Content addressing: Git, IPFS and deduplicationIn content-addressed storage the key is a hash of the value. Two identical files have the same key automaticallylesson
Cache keys, ETags and shard keysA cache key must include every input that changes the output. Miss one — a locale, a feature flag, the requestinglesson
Collisions, birthday attacks and length extensionThe birthday bound says a collision becomes likely after roughly the square root of the output space. For a 128-bitlesson
SHA-3, BLAKE2 and BLAKE3BLAKE3 splits the input into chunks, hashes them in parallel and combines them with a Merkle tree. That makes it thelesson
Digital signatures and certificatesPublic-key operations are slow and only work on small inputs, so a signature covers a hash of the message. That is whylesson
Migrating off MD5 and SHA-1 safelyA big-bang switch breaks every stored value at once. The safe pattern is to store both digests, verify against eitherlesson

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 recomputing

Full 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 SHA256

Full 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 -4

Full 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 md5

Full 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 -blake2b512

Full 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 -dates

Full 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?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 13 lessons of the Hashing & Checksums course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full Hashing & Checksums course — it carries the worked explanations, the edge cases and the exercises behind every line here.

Algorithms Data Structures Computer Networks Operating Systems Character Encodings Data Formats

Last refreshed 2026-09-27.