Verify File Integrity with Checksums on Linux and Windows
Next to every serious software download — a Linux ISO, a Node.js installer, a database release — you will find a string like a1b2c3... labelled SHA-256. Most people ignore it. That string is a checksum: a fingerprint of the exact bytes the publisher released, and comparing it against a hash of your downloaded copy is the only way to know you got those bytes and not a corrupted — or tampered — file. This guide shows the exact commands on Linux, macOS, and Windows, what a mismatch means, and how to automate verification in CI so it never depends on someone remembering to do it.
Why Checksums Matter
A hash function like SHA-256 maps any file to a fixed-size digest, and a change to even one bit of the file produces a completely different digest. That single property catches two very different failure modes:
- Accidental corruption — interrupted downloads, flaky Wi-Fi, failing disks, proxy mangling. A 4 GB ISO that lost a few packets will hash differently, and you find out before a cryptic install failure at minute 40.
- Deliberate tampering — compromised mirrors and supply-chain attacks are not hypothetical. In 2016, the official Linux Mint site was hacked and its ISO replaced with a backdoored version; users who checked the checksum caught it, users who did not got malware with root access. Package repositories and CDNs have had similar incidents.
One caveat: if an attacker controls the same page that hosts both the file and the hash, they can replace both. Checksums are strongest when the hash comes from a different channel than the file (the project's HTTPS site vs a mirror), or when the checksum file itself is GPG-signed.
Linux and macOS: sha256sum
# Hash a single file sha256sum ubuntu-24.04.iso # 8d1b...f3a9 ubuntu-24.04.iso # macOS ships shasum instead shasum -a 256 ubuntu-24.04.iso # Best practice: verify against the publisher's checksum file # SHA256SUMS contains lines like: <hash> <filename> sha256sum -c SHA256SUMS --ignore-missing # ubuntu-24.04.iso: OK # Compare a pasted hash without eyeballing 64 characters echo "8d1b...f3a9 ubuntu-24.04.iso" | sha256sum -c -
The -c (check) mode is the safest workflow: the tool does the comparison and prints OK or FAILED, eliminating the very human mistake of comparing only the first and last few characters of a long hex string.
Windows: certutil and Get-FileHash
:: Command Prompt (built into every Windows version) certutil -hashfile node-v22.11.0-x64.msi SHA256 :: SHA256 hash of node-v22.11.0-x64.msi: :: 5f8c1e...9d2b # PowerShell (cleaner output, defaults to SHA256) Get-FileHash .\node-v22.11.0-x64.msi # Other algorithms Get-FileHash .\file.zip -Algorithm SHA512 # Automatic comparison — prints True or False (Get-FileHash .\file.zip).Hash -eq "5F8C1E...9D2B"
Note that Get-FileHash outputs uppercase hex while most published hashes are lowercase — PowerShell's -eq is case-insensitive for strings, so the comparison above works either way.
Reading a Mismatch Correctly
- First mismatch: almost always a corrupted or incomplete download. Check the file size, then re-download.
- Repeated mismatch from one mirror: try the project's primary site. Mirrors sometimes serve stale or broken files.
- Mismatch from a clean primary source: stop. Do not run the file. Confirm you are hashing the right file with the right algorithm (SHA-256 hash compared against an SHA-512 value will obviously never match), then report it to the project.
- Everything matches: you have byte-for-byte what the publisher hashed — corruption ruled out, and tampering ruled out to the extent you trust the channel the hash came from.
Automating Checksum Verification in CI
Build pipelines download tools, binaries, and base artifacts constantly, and CI is exactly where a poisoned download does the most damage. Pin the expected hash in the repository and fail the build on mismatch:
# GitHub Actions / any bash-based CI
- name: Download and verify tool
run: |
curl -sSLo tool.tar.gz https://example.com/tool-1.4.2.tar.gz
echo "d3ab...77e1 tool.tar.gz" | sha256sum -c - \
|| { echo "CHECKSUM MISMATCH — aborting build"; exit 1; }
tar -xzf tool.tar.gz
# Node.js script version (cross-platform)
const crypto = require('crypto');
const fs = require('fs');
const EXPECTED = 'd3ab...77e1';
const hash = crypto.createHash('sha256')
.update(fs.readFileSync('tool.tar.gz'))
.digest('hex');
if (hash !== EXPECTED) {
console.error('Checksum mismatch:', hash);
process.exit(1);
}- Commit expected hashes to the repo so changes go through code review — a bumped hash in a diff is a visible, auditable event
- This is the same idea behind lockfile integrity: npm's
package-lock.jsonstores an SHA-512 hash per package and refuses tampered tarballs automatically - Publish checksums for your own release artifacts too —
sha256sum dist/* > SHA256SUMSas a release step costs one line
Frequently Asked Questions
On Linux and macOS run sha256sum filename (or shasum -a 256 filename on macOS). On Windows run certutil -hashfile filename SHA256 or Get-FileHash filename in PowerShell. Compare the output with the publisher's hash — they must match exactly.
Your file is not byte-for-byte the file the publisher hashed. Usually the download was corrupted — re-download it. If it still fails from a clean source, treat the file as untrustworthy and do not run it.
SHA-256 is the standard: it detects both accidental corruption and deliberate tampering. MD5 and SHA-1 still catch accidental corruption but are broken against attackers who can craft collisions, so prefer SHA-256 whenever it is offered.