DEV Community

Max
Max

Posted on Originally published at orthogonal.info

A Matching Checksum Doesn't Prove What You Think: Integrity vs Authenticity

Last week I downloaded a CLI release and saw the familiar pair of links: a binary and its SHA256SUMS file. I hashed the binary, the digest matched, and I felt safe for about five seconds. Then I noticed the binary and its checksum came from the same server.

If that server were compromised, an attacker could have swapped both. The hash would still match perfectly. It just would not answer the question I actually cared about: did this file come from the real publisher?

That gap — integrity vs authenticity — trips up a lot of otherwise careful developers. Here's how I reason about it.

What a matching hash actually proves

A cryptographic hash turns any number of bytes into a fixed-length fingerprint. SHA-256 always returns 256 bits, shown as 64 hex characters. Flip one bit of input and the output changes unpredictably.

So when your computed SHA-256 equals the value a publisher lists, you have strong evidence that your local file is byte-for-byte identical to the file the publisher hashed. That catches:

  • an incomplete or resumed download
  • disk / transfer corruption
  • a broken or stale mirror
  • an accidental artifact replacement

That's real and useful. It is also the entire claim. A match says two byte sequences agree. It says nothing about who produced those bytes.

Integrity is not authenticity

Picture a release page serving tool.tar.gz and SHA256SUMS from the same origin. An attacker who controls that origin uploads a backdoored archive, recomputes its SHA-256, and overwrites the checksum file. Your comparison passes — because both malicious artifacts agree with each other.

HTTPS doesn't save you here. TLS protects the connection between your browser and the server. It says nothing about whether the server, build pipeline, maintainer account, or artifact was trustworthy before that connection began.

A checksum earns its keep when the reference value arrives over an independent, authenticated path: a signed checksum manifest, a package manager with signed metadata, a maintainer's verified channel, or a separately-controlled domain. The independence matters far more than the visual length of the hash.

MD5 and SHA-1 need careful language

MD5 and SHA-1 are broken for collision resistance. In 2017, the SHAttered work (Google + CWI Amsterdam) produced two visibly different PDFs with the same SHA-1 digest. MD5 fell years earlier.

But be precise about what that means, because people overstate it:

  • A collision = an attacker crafts two inputs that share a hash (they control both).
  • A second-preimage attack = an attacker takes your existing file and manufactures a different malicious file with the same digest. That's a harder, separate problem.

Collisions alone are still enough to make MD5/SHA-1 unacceptable whenever an adversary can shape both artifacts (chosen-prefix collisions). Keep MD5 around only for identifying old files or matching a legacy checksum — never as proof against an attacker. SHA-256 is the sensible default; no practical SHA-256 collision is known.

The download check I actually run

  1. Download the artifact from the publisher's HTTPS release page.
  2. Find the publisher's SHA-256 value — prefer a signed checksum manifest or an independent official channel, not the same box that served the binary.
  3. Compute the digest locally.
  4. Compare the full 64-char digest, not the first/last few characters.
  5. If the project offers a signature, verify it as a separate step. A matching checksum does not replace signature verification.

For repeatable builds, pin the expected value and fail closed:

# macOS
shasum -a 256 tool.tar.gz

# Linux
sha256sum tool.tar.gz

# PowerShell
Get-FileHash .\tool.zip -Algorithm SHA256
Enter fullscreen mode Exit fullscreen mode

Never paste a shortened digest into CI. A full SHA-256 is only 64 characters; truncating it deliberately throws away collision resistance. Store the expected value in reviewed source control, or consume a signed manifest.

When a signature is the real answer

A digital signature binds a digest to a private key, and verification proves the signer controlled that key. That's the authenticity property a bare checksum can't give you (GPG, platform code signing, or Sigstore in modern projects).

The catch just moves: how do you trust the public key or identity? A signature from an unknown key is worthless. Look for a fingerprint published on the project's established site, a verified maintainer identity, a package ecosystem's trust root, or Sigstore's identity + transparency-log checks.

(And HMAC is a different thing again — it authenticates data between parties sharing a secret, which is why it's great for webhook signatures but useless for public downloads: you can't safely hand every visitor the secret.)

A five-second threat model

Risk Does a plain SHA-256 comparison help? Better control
Accidental corruption Yes Checksum comparison
Broken / stale mirror Yes, with an independent reference Official checksum
Network tampering Somewhat HTTPS + independent checksum
Compromised download server No, if it hosts both files Signed release manifest
Malicious publisher / stolen signing key No Reproducible builds, transparency logs, key revocation

What I trust in practice

  • Low-risk utility: HTTPS + a SHA-256 from the official release page is fine.
  • Installer, firmware, wallet, security tool, production dependency: I want a signature or signed package metadata too.
  • High-impact infrastructure: reproducible builds or a transparency log on top.

One more privacy note: if the artifact is proprietary, contains customer data, or comes from an internal build, don't upload it to a random online checksum site just to answer a local math question — that creates a new disclosure risk. I keep integrity checks browser-only for exactly that reason: I built HashForge as a client-side hasher that runs crypto.subtle.digest() in your own browser (SHA-1/256/384/512, plus a local JS MD5 for legacy matching) so the file never leaves your machine. Use it when you need a fast manual check; use sha256sum + a signed manifest when a build must fail closed.

I wrote up the longer version — including the reproducible-builds and Sigstore angles — here.

Keep the claim precise: a matching hash proves two byte sequences agree. It does not tell you who created those bytes.

What's the worst "the checksum matched, so it's safe" assumption you've seen ship — and did anything catch it before prod?

Top comments (0)