ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 69

  1. Blog

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 Challa·30 September 2026·4 min read

Summary
On this page
  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — compare (p50 Mops/s)
  5. MAC construct (p50)
  6. Reading it
  7. Security framing
  8. Why the slowdown is acceptable
  9. Unequal-path note
  10. Construct vs compare
  11. Pitfalls
  12. Reproduce
  13. Limits
  14. Takeaway

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

ArmRole
hmac.compare_digest(a,b)recommended verify
a == bnaive equality
equal / unequal digestsboth paths
hmac.new(...).digest()MAC construct
hashlib.sha256honesty non-MAC contrast

Lab topology

compare: 500_000 ops · 7 rounds · p50
MAC: 100_000 digests · payloads 64 / 1024 / 8192 B

Script: lab-evidence/96-hmac-compare-digest/results/run_lab.py.


Lead table — compare (p50 Mops/s)

ArmMops/s
bytes == equal29.68
compare_digest equal15.63
bytes == unequal42.08
compare_digest unequal20.75

MAC construct (p50)

Payloadhmac.new kops/shmac.update kops/ssha256 (not MAC) kops/s
64 B623.4598.52335.0
8 KiB146.0143.5176.0

Reading it

  • Naive == is faster on this microbench — that is not a reason to use it for secrets.
  • compare_digest is 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

NeedUse
Compare two digests/tokenshmac.compare_digest
Compute keyed MAChmac.new(key, msg, sha256)
Fast content hash (non-auth)hashlib (see lab 77)
Random secret bytessecrets (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

python3 lab-evidence/96-hmac-compare-digest/results/run_lab.py

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.

hmac.compare_digestconstant-time comparehmac-sha256token verifypython hmaclocalhost labsreops/s

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/.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

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

On this page

  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — compare (p50 Mops/s)
  5. MAC construct (p50)
  6. Reading it
  7. Security framing
  8. Why the slowdown is acceptable
  9. Unequal-path note
  10. Construct vs compare
  11. Pitfalls
  12. Reproduce
  13. Limits
  14. Takeaway
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove