MD5 Hash a File: md5sum, macOS md5, PowerShell Get-FileHash, certutil and Python
Every operating system can compute a file's MD5 checksum with a built-in command, but each prints it differently, and that is where most "checksum mismatch" reports come from. This page lists the commands for Linux, macOS and Windows with their real output for one test file, shows how to verify a published checksum automatically, and gives streaming code for scripts that hash large files. The test file contains exactly `The quick brown fox jumps over the lazy dog`, with no newline, so its MD5 is `9e107d9d372bb6826bd81d3542a419d6` everywhere.
Linux: md5sum
md5sum is part of GNU coreutils and present on every mainstream distribution. It prints the hash, two spaces and the file name, which is also the format of .md5 checksum files:
$ md5sum fox.txt
9e107d9d372bb6826bd81d3542a419d6 fox.txt
$ md5sum *.iso > checksums.md5 # hash many files into one list
$ md5sum fox.txt | cut -d' ' -f1 # just the hash, for scripts
If a line shows * before the file name instead of a second space, it was produced in binary mode (-b). On Linux both modes give the same hash; the marker only matters on systems that translate line endings when reading text files. openssl dgst -md5 fox.txt works too and prints MD5(fox.txt)= 9e107d….
macOS: md5
macOS ships BSD md5 rather than md5sum. Its default output puts the hash at the end; -q prints only the hash and -r switches to the md5sum order:
$ md5 fox.txt
MD5 (fox.txt) = 9e107d9d372bb6826bd81d3542a419d6
$ md5 -q fox.txt
9e107d9d372bb6826bd81d3542a419d6
$ md5 -r fox.txt
9e107d9d372bb6826bd81d3542a419d6 fox.txt
Recent macOS releases also install md5sum as a second name of the same program, in /sbin, and in that mode the options and output match GNU coreutils, including --check. Older releases have only md5, and Homebrew's coreutils package may put GNU md5sum earlier in your PATH; run which -a md5sum to see which one you get. Scripts that must run on any Linux and any macOS version should test which command exists, or use openssl dgst -md5, which is available on both.
Windows: Get-FileHash and certutil
In PowerShell, Get-FileHash computes MD5 when you ask for it explicitly; its default algorithm is SHA-256. The hash comes back in upper case:
PS> Get-FileHash -Algorithm MD5 .\fox.txt
Algorithm Hash Path
--------- ---- ----
MD5 9E107D9D372BB6826BD81D3542A419D6 C:\data\fox.txt
PS> (Get-FileHash -Algorithm MD5 .\fox.txt).Hash.ToLower()
9e107d9d372bb6826bd81d3542a419d6
From the classic command prompt, certutil does the same. It prints a header line, the hash, and a status line:
C:\> certutil -hashfile fox.txt MD5
MD5 hash of fox.txt:
9e107d9d372bb6826bd81d3542a419d6
CertUtil: -hashfile command completed successfully.
Write the algorithm name in capitals: Windows 7 accepted only MD5, while later versions accept any case. Hex digests are case-insensitive, so 9E107D… and 9e107d… are the same checksum; compare them with a case-insensitive comparison, not by eye.
Verifying a downloaded file against its checksum
When a project publishes an .md5 file, let the tool do the comparison. md5sum -c reads each line, hashes the named file and reports the result; its exit status is non-zero if anything fails, which makes it safe to use in scripts:
$ md5sum -c fox.md5
fox.txt: OK
# after the file was modified by a single byte:
$ md5sum -c fox.md5
fox.txt: FAILED
md5sum: WARNING: 1 computed checksum did NOT match
$ echo $?
1
On macOS, md5 -c takes the expected hash as a string rather than a file: md5 -c 9e107d9d372bb6826bd81d3542a419d6 fox.txt prints the usual line, appends [ Failed ] on a mismatch and exits with status 2. Where md5sum is available, md5sum -c behaves as on Linux. In PowerShell, compare strings with -eq, which is case-insensitive by default: (Get-FileHash -Algorithm MD5 file).Hash -eq $expected.
Python and Node.js for large files
Never read a large file into memory just to hash it. Python 3.11 added hashlib.file_digest, which streams the file for you; on older versions, feed the hash object in chunks:
import hashlib
with open("fox.txt", "rb") as f: # Python 3.11+
print(hashlib.file_digest(f, "md5").hexdigest())
h = hashlib.md5() # any Python 3
with open("fox.txt", "rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
print(h.hexdigest()) # 9e107d9d372bb6826bd81d3542a419d6
In Node.js, pipe a read stream into the hash, which is itself a writable stream:
const { createHash } = require("node:crypto");
const { createReadStream } = require("node:fs");
const { pipeline } = require("node:stream/promises");
const h = createHash("md5");
await pipeline(createReadStream("fox.txt"), h);
console.log(h.digest("hex")); // 9e107d9d372bb6826bd81d3542a419d6
Always open files in binary mode ("rb" in Python). Text mode on Windows converts line endings and produces a hash of different bytes. PHP has md5_file(), which also streams.
Why the same file gives different MD5 hashes
The algorithm is deterministic, so a different hash means different bytes. The usual causes are line endings (line followed by CRLF hashes to 957774f5…, with LF only to 5b4bd981…), a Git setting such as core.autocrlf rewriting files on checkout, an FTP transfer in ASCII mode, a trailing newline added by an editor, or a file that was still being written when it was hashed.
Hashing a pasted string instead of the file is another frequent mix-up. Text typed into a web form loses or gains newlines and may be re-encoded; to compare with a file checksum, hash the file itself. An in-browser hash generator is handy for strings and short samples.
Finally, remember what MD5 can and cannot prove. It reliably detects accidental corruption, but since practical collision attacks exist, it does not prove that a file was not tampered with by someone who could also change the published checksum. For security-relevant downloads, use the SHA-256 checksum and, ideally, a signature. The MD5 hash length article explains the output format.
Sources and further reading
The GNU coreutils manual documents md5sum, its output format and --check. The macOS md5(1) manual page covers -q and -r. Microsoft documents Get-FileHash and certutil -hashfile. The Python hashlib documentation describes file_digest (added in 3.11), and the Node.js crypto and stream documentation covers hashing streams. RFC 1321 defines MD5 and RFC 6151 its security limits.
Frequently asked questions
How do I get the MD5 hash of a file?
Use md5sum file on Linux, md5 file on macOS, Get-FileHash -Algorithm MD5 file in PowerShell, or certutil -hashfile file MD5 in the Windows command prompt. All four print the same 32-character hash.
How do I verify an MD5 checksum file?
Run md5sum -c file.md5 in the directory with the files. It prints OK or FAILED for each entry and returns a non-zero exit status if any checksum does not match.
Why does Get-FileHash show the MD5 in capital letters?
PowerShell formats hashes in upper-case hexadecimal. Hex is case-insensitive, so it is the same value as the lower-case output of md5sum; use .ToLower() if you need to match exactly.
Why does the same file have a different MD5 on Windows and Linux?
The bytes differ, most often because of line endings: CRLF on Windows versus LF on Linux, changed by Git's autocrlf setting, an editor or an ASCII-mode transfer.
How do I MD5 hash a large file in Python?
Use hashlib.file_digest(f, "md5") on Python 3.11 or later, or read the file in chunks and call update() on an md5 object. Open the file in binary mode.
Is an MD5 checksum enough to verify a download?
It detects accidental corruption reliably. Because MD5 collisions can be produced deliberately, use SHA-256 and a signature when you need to detect tampering.