Hashes, passwords, and MACs
Use hashes for fingerprints
Use a modern cryptographic hash to identify bytes and detect accidental or adversarial changes when the expected digest is trusted.
A cryptographic hash turns any input into a fixed-size digest. A small input change produces a very different result, which makes the digest a reliable fingerprint for bytes.
SHA-256 is the standard choice today. It gives you a 32-byte digest, usually written as 64 hex characters:
shasum -a 256 app.zip
# 7c2df1a9e3b0f4d6... app.zip
Hashes help identify files, content, and structured messages. Compute the digest on both ends of a transfer, compare, and you know the bytes match. Content-addressed systems like Git are built on exactly this idea.
Verifying a download
The common workflow ships a checksum file next to the artifact:
shasum -a 256 -c app.zip.sha256
# app.zip: OK
The OK means the file matches the digest in app.zip.sha256. Change one byte of the file and the same command reports FAILED.
The trust problem
Now the part everyone skips. A plain downloaded checksum does not authenticate a file if an attacker can replace both file and checksum.
A download page serves app.zip and its SHA-256 checksum from the same compromised server. The attacker replaces both, and the user sees a successful comparison. The math worked perfectly. The trust assumption was wrong.
The digest detects changes only when the expected value has an independent trusted path. That path can be a different server, a value you recorded earlier, or a digest published over a channel the attacker does not control. A signature can supply that path when its verification key is already trusted, which is why serious projects sign their release checksums.
So when you verify a fingerprint, ask where the expected digest came from. If the answer is “the same place as the file”, you verified transfer integrity and nothing more. That is still useful against corruption. It is not useful against an attacker.
Inside your own code you can compute digests with node:crypto:
import { createHash } from 'node:crypto'
import { readFileSync } from 'node:fs'
createHash('sha256').update(readFileSync('app.zip')).digest('hex')
Practice
Hash a small file, save the digest through a separate trusted channel, and verify it from a clean copy. Change one byte and capture the failed check. Then replace both the file and its adjacent checksum and explain why the command succeeds but authenticity still fails.
Lesson completed