security
How to Verify a SHA-256 Checksum (and What It Proves)
How to verify a SHA-256 checksum on Windows, macOS and Linux, why a matching hash proves less than you think, and what to check when it does not match.
You download a 5 GB ISO, the page says SHA256: 9bc6..., you run the command, the strings match, and you feel safe. A matching checksum proves exactly one thing: the bytes on your disk are the bytes that produced that digest. It says nothing about who produced the digest. If the hash was printed on the same page that served the download, an attacker who can change one can change the other — and has. Here are the commands for every OS, then the part most guides skip.
The commands, per OS
| OS / shell | Command |
|---|---|
| Windows (PowerShell) | Get-FileHash .\ubuntu.iso |
| Windows (PowerShell, other algorithm) | Get-FileHash .\ubuntu.iso -Algorithm MD5 |
| Windows (cmd, no PowerShell) | certutil -hashfile ubuntu.iso SHA256 |
| macOS | shasum -a 256 ubuntu.iso |
| Linux | sha256sum ubuntu.iso |
| Anything with OpenSSL | openssl dgst -sha256 ubuntu.iso |
Four details that trip people up:
Get-FileHashdefaults to SHA-256. You only pass-Algorithmwhen you want something else. It prints uppercase hex in a table; the comparison you actually want is(Get-FileHash .\ubuntu.iso).Hash -eq '9bc6...', which returnsTrueorFalse. PowerShell’s-eqis case-insensitive on strings, so uppercase output versus a lowercase digest on the download page is not a problem. (-ceqis the case-sensitive one, and you don’t want it here.)certutiloutput is chattier than it looks. It prints a header line, the digest, and aCertUtil: -hashfile command completed successfully.footer. On Windows 7 and 8 it also printed the digest as space-separated byte pairs; Windows 10 and 11 print one continuous string. Strip spaces before comparing if you’re scripting against it.- macOS has
shasum, and recent versions also havesha256sum. macOS 26 ships an Apple-built/sbin/sha256sum, but it’s a slim reimplementation —sha256sum --helpthere printsusage: sha256sum [-bctwz], with none of GNU coreutils’ long options.shasum -a 256has been on every Mac for years and is the portable choice. openssl dgstlabels its own output. Modern OpenSSL printsSHA2-256(ubuntu.iso)= 9bc6..., notSHA256(...). Same digest, different label — don’t let the prefix convince you that you hashed the wrong thing.
If you’d rather not think about any of this, drop the file on the File Checksum Verifier: it hashes locally in your browser and tells you match or mismatch in a colored banner.
Verifying against a .sha256 or SHA256SUMS file
Serious projects don’t publish a single digest on a page — they publish a checksum file listing every artifact, which is what you should be checking against.
sha256sum -c SHA256SUMS
# ubuntu-24.04.1-desktop-amd64.iso: OK
On a mismatch you get both a per-file line and a summary, and a non-zero exit code:
ubuntu-24.04.1-desktop-amd64.iso: FAILED
sha256sum: WARNING: 1 computed checksum did NOT match
GNU coreutils’ --ignore-missing is essential here, because SHA256SUMS usually lists a dozen images and you downloaded one:
sha256sum -c --ignore-missing SHA256SUMS
macOS equivalent, using the checksum file the publisher shipped:
shasum -a 256 -c ubuntu.iso.sha256
The file format is worth recognizing on sight, because two dialects are in circulation. GNU tools emit <digest> <filename> — two spaces, or a space and an asterisk for binary mode. BSD tools emit SHA256 (ubuntu.iso) = 9bc6..., and OpenSSL a close cousin of it (SHA2-256(ubuntu.iso)= 9bc6...). All are valid; a parser that only knows one of them will reject the others.
The trust ladder
This is the part that matters, and it’s a ladder, not a yes/no.
| What you verified | What it proves | What it does not |
|---|---|---|
| Hash printed on the same page as the download | Your copy is byte-identical to what that page described | Nothing about whether the page is honest or intact |
| Hash from a different host — distro mirror index, package manager, release notes on a separate domain | The download host alone can’t lie to you; two systems must be compromised | Still nothing about the publisher’s intent |
| Detached signature (GPG, minisign, signify, Sigstore) checked against a key you obtained independently | The artifact was signed by the holder of that key | That the key holder is trustworthy |
Rung one is integrity only. It catches a truncated download, a flaky mirror, a corrupted USB write — which is genuinely most of what goes wrong, and why checksums are worth running. It does not catch tampering, because a compromised web server serves the hash and the file from the same place.
That is not hypothetical. In February 2016 the Linux Mint website was compromised: the download links were pointed at a backdoored 17.3 Cinnamon ISO carrying an IRC-controlled bot, and the checksums displayed on the site were changed to match. Every user who dutifully compared the hash to the page got a green light.
Rung three is the fix, and it’s a two-step dance. Ubuntu, for example, publishes SHA256SUMS alongside SHA256SUMS.gpg:
gpg --verify SHA256SUMS.gpg SHA256SUMS
sha256sum -c --ignore-missing SHA256SUMS
The signature authenticates the list; the hashes then authenticate the files. Both steps are load-bearing, and the whole thing still rests on having the right public key — a key you fetched from the same compromised page proves nothing. This is the same structural idea as a keyed HMAC: an unkeyed hash can be recomputed by anyone, including whoever swapped the file, so authenticity always requires a secret or a key you already trust.
And the ceiling is real: in 2024 the xz-utils backdoor shipped inside release tarballs signed by the project’s own maintainer. Signatures prove provenance, not good intentions.
”The checksum doesn’t match” — work down this list
In roughly descending order of likelihood:
- Partial or resumed download. Check the file size against the published size first; it’s a one-second test that explains most mismatches.
- Wrong file.
amd64vsarm64,24.04vs24.04.1, the torrent vs the direct download. The digest is for one exact artifact. - Different algorithm. You’re comparing an MD5 to a SHA-256. Count hex characters: 32 = MD5, 40 = SHA-1, 64 = SHA-256, 128 = SHA-512. If the lengths differ, you never had a chance.
- Copy-paste damage. A trailing space, a line-wrapped digest that picked up a newline mid-string, or an invisible character from a docs site’s styled code block. Digests are case-insensitive hex; they are not whitespace-tolerant in a naive
==. - Line-ending conversion. If a text file or script passed through anything that rewrote LF to CRLF — a text-mode transfer, an editor,
core.autocrlfon Windows — the bytes changed, so the hash changed. (This is not the-b/-tflag: on POSIX systems binary and text mode compute the same digest.) - You hashed the wrong thing. The
.zipinstead of the extracted binary, the.sha256sidecar instead of the payload, or a symlink. - The file really is different. Re-download from another mirror. If it mismatches again, stop and go look for a signature.
Before you trust the file once it does match, it’s also worth knowing that a checksum says nothing about a file’s type — see magic bytes and file signatures for how software actually decides what a file is.
MD5 and SHA-1: fine for corruption, useless against tampering
The distinction is between two attacks. A collision means an attacker crafts two files with the same digest — practical for MD5 since 2004 and demonstrated for SHA-1 by Google and CWI’s SHAttered in 2017. A second preimage means finding a different file matching a digest someone else already published; that remains infeasible even for MD5.
So if a publisher gave you an MD5 years ago and you’re checking a download against it, a random attacker can’t easily forge a match. But a malicious or compromised publisher can prepare a benign file and an evil file with the same MD5 in advance, get the benign one blessed, and ship the other — which is roughly how the Flame malware forged a Microsoft code-signing certificate in 2012 with a chosen-prefix MD5 collision.
The rule: MD5 and SHA-1 detect accidents, not adversaries. CRC32 detects only transmission errors and isn’t a cryptographic hash at all. Use SHA-256 or BLAKE3 when you get to choose, and when you don’t, use whatever the publisher used — but know which rung of the ladder you’re standing on. (For hashing short strings rather than files, the Hash Generator covers MD5 through SHA-512; when a shared secret is involved, you want the HMAC Generator instead.)
How a browser hashes a 4 GB file without uploading it
The obvious objection to a web-based checker is that it must be uploading your file. It isn’t, and the mechanism is worth knowing because it also explains why the built-in browser crypto API can’t do this job.
crypto.subtle.digest() — the Web Crypto API — takes a complete buffer. To hash a 4 GB ISO with it you’d have to materialize 4 GB in memory first. So our verifier uses hash-wasm, a WebAssembly implementation with an incremental interface: init(), update(chunk), update(chunk), …, digest(). The file is read through Blob.slice() in 8 MiB slices, each slice is fed to the hasher and discarded, and memory stays flat no matter how big the file is. The hasher reports progress after every slice, which is what feeds the progress bar and what gives Cancel a place to take effect.
Nothing leaves your machine — not the bytes, not the filename. The only request the tool itself makes is a one-time fetch of the lazy-loaded hashing chunk (hash-wasm ships its WebAssembly base64-inlined into JavaScript, so there is no separate .wasm to download). Watch the Network tab while you hash: no upload, whatever the file size.
What to do
- Always verify, even from a page you trust — it catches the corrupt-download case, which is the common one.
- Get the hash from somewhere other than the download page when you can: the project’s signed release notes, a package index, a mirror on a different domain.
- For anything with real consequences, verify a signature, not just a hash, and get the key from a channel you already trust.
- Compare by tooling, not by eye. Humans checking 64 hex characters read the first four, the last four, and nothing in between.
- Drop the file and the expected value into the File Checksum Verifier. It handles CRC32, MD5, SHA-1, SHA-256, SHA-512, SHA3-256 and BLAKE3; guesses the algorithm from the digest length and offers to enable it; accepts a bare hex digest in any case (with spaces, colons or dashes between bytes), a base64 digest, a pasted
sha256sumorSHA256 (file) = ...line, or an entireSHA256SUMSfile matched by filename. Drop the.sha256sidecar onto the dropzone and it fills the expected box for you — then drop the download and read the banner.