Plate 69
hmac.compare_digest vs ==: Localhost Lab
Hands-on hmac.compare_digest vs bytes == verify lab: real ops/s plus HMAC-SHA256 construct costs, measured on Linux localhost in this eng lab for SREs.
Aditya Challa4 min read
Intro — what this post promises
Verifying an auth digest: hmac.compare_digest vs naive ==, plus HMAC-SHA256 construct throughput. This lab reports ops/s on Linux localhost.
It is not a hashlib algorithm bake-off (md5 vs blake2b) and not CSPRNG minting (secrets vs urandom). This is not a timing-attack proof — we measure throughput and recommend compare_digest for tokens/MACs anyway.
Related links:
- hashlib md5 vs blake2b localhost lab
- secrets vs urandom localhost lab
- shelve vs pickle dict localhost lab
- subprocess run vs popen localhost lab
- futures as completed vs wait localhost lab
- fnmatch vs re localhost lab
- scandir vs listdir localhost lab
- tarfile vs zipfile localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. No Docker. Equal and unequal 32-byte digests.
Verdict up front: == on equal digests ~29.68 Mops/s vs compare_digest ~15.63 Mops/s (~1.9×). HMAC-SHA256 construct B: ~623.4 kops/s (raw hashlib.sha256 is faster and not a MAC).
Arms
| Arm | Role |
|---|---|
hmac.compare_digest(a,b) | recommended verify |
a == b | naive equality |
| equal / unequal digests | both paths |
hmac.new(...).digest() | MAC construct |
hashlib.sha256 | honesty non-MAC contrast |
Lab topology
Script: lab-evidence/96-hmac-compare-digest/results/run_lab.py.
Lead table — compare (p50 Mops/s)
| Arm | Mops/s |
|---|---|
bytes == equal | 29.68 |
| compare_digest equal | 15.63 |
bytes == unequal | 42.08 |
| compare_digest unequal | 20.75 |
MAC construct (p50)
| Payload | hmac.new kops/s | hmac.update kops/s | sha256 (not MAC) kops/s |
|---|---|---|---|
| 64 B | 623.4 | 598.5 | 2335.0 |
| 8 KiB | 146.0 | 143.5 | 176.0 |
Reading it
- Naive
==is faster on this microbench — that is not a reason to use it for secrets. compare_digestis the stdlib API intended for secret comparisons; use it for tokens, password hashes, cookies, HMAC tags.- HMAC ≠ hashlib: hashing a payload without a keyed MAC construction is a different primitive (shown only as contrast).
- Do not treat these timings as a side-channel audit.
Security framing
| Need | Use |
|---|---|
| Compare two digests/tokens | hmac.compare_digest |
| Compute keyed MAC | hmac.new(key, msg, sha256) |
| Fast content hash (non-auth) | hashlib (see lab 77) |
| Random secret bytes | secrets (see lab 88) |
Why the slowdown is acceptable
Auth compares are rarely the hot path next to network or DB. Paying ~1.9× on a 32-byte compare is cheap insurance versus shipping a short-circuiting == into a token check. Optimize MAC generation or I/O before shaving compare_digest.
Unequal-path note
Unequal digests made both == and compare_digest faster here (early exit vs still scanning). Production verifiers should assume attackers control one side — that is exactly why short-circuiting equality is the wrong default for secrets even when it wins a microbench.
Construct vs compare
HMAC construction (~hundreds of kops/s at 64 B) dwarfs compare cost (~tens of Mops/s). If verify is hot, you are usually recomputing the MAC or hitting I/O — not losing to compare_digest. Profile before replacing stdlib safety calls.
Pitfalls
- Using
==on password hashes / API tokens because a blog showed Mops/s. - Claiming “constant-time” from a userspace microbench alone.
- Rolling a manual HMAC with string concat + sha256.
- Comparing digests of different lengths without
compare_digest(it also length-checks safely).
Reproduce
Evidence: summary.json, summary.txt.
Limits
One Linux box. Throughput only. Not hardware timing analysis. SHA-256 only.
Takeaway
== ~29.68 Mops/s beat compare_digest ~15.63 Mops/s here — still use hmac.compare_digest for auth verifies. Build MACs with hmac, not bare hashlib, and keep CSPRNG/secrets for key material.
Lab evidence
What I found running this
Lab 1 Oct 2026 IST. Python 3.13.5. compare equal: == 29.68 Mops/s vs compare_digest 15.63 (~1.9x). HMAC@64B 623.4 kops/s. Not timing-attack proof; not labs 77/88. Affiliates: 0. Evidence: lab-evidence/96-hmac-compare-digest/.
Related links
Plate 17
platform vs os.uname Inventory: Localhost Lab
Hands-on platform.platform vs os.uname host inventory lab: real ops/s plus cache notes, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026
Plate 75
uuid.uuid4 vs uuid.uuid1: Localhost Lab
Hands-on uuid.uuid4 vs uuid.uuid1 ID generation lab: real ops/s plus version/node checks, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026
Plate 76
cmath vs math.hypot Magnitudes: Localhost Lab
Hands-on cmath vs math.hypot magnitude ops lab: real ops/s for abs, polar, and phase, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026