Security

How to Verify a File Hash (SHA-256, MD5) on Windows, Mac, and Linux

2026-05-31 · 9 min read · Generate hashes free →

You download a Linux ISO, a software installer, a database binary, or a cryptography library. Next to the download link sits a long string of hexadecimal characters — the SHA-256 or MD5 checksum. How do you use it? What does it actually prove when it matches? What should you do when it does not? This guide covers verification on every platform — Windows, macOS, Linux — plus Python scripts for automation and CI/CD pipelines, and explains precisely what file hash verification protects you from and what it cannot detect.

What a File Hash Is and What It Proves

A cryptographic hash function takes a file of any size — a 1KB config file or a 4GB ISO image — and produces a fixed-length output called a hash, checksum, or digest. SHA-256 always produces exactly 64 hexadecimal characters. The defining mathematical property: any change to the input, even a single bit, produces a completely different output with no discernible relationship to the original. The outputs look like random noise regardless of whether the change was tiny or large.

This property gives hash verification its power. If you compute the hash of a downloaded file and it matches the hash the publisher computed and published: you have cryptographic proof that your copy is bit-for-bit identical to the publisher's copy at the time they computed the hash. No corruption occurred during the download — no network error, no interrupted transfer, no storage fault. No one modified the file in transit between the publisher's server and your machine.

Understanding what a matching hash does not prove is equally important. It does not prove the software is free from vulnerabilities, that the publisher can be trusted, or that the publisher's servers were not compromised before they published the file. If an attacker compromised the distribution server and replaced both the file and the published hash on the same page, hash verification using only that page's hash cannot detect the attack — the attacker can publish a hash that matches their malicious file. For maximum assurance, verify the publisher's PGP signature of the hash from a separately trusted source, or obtain the hash from multiple independent sources.

Step-by-Step: Verifying a Download (Practical Walkthrough)

This walkthrough uses a real software download — Python 3.12 — to illustrate the complete process.

Step 1: Download the file from the official website only. Do not download from mirrors or third-party aggregators for security-sensitive software.

Step 2: Find the published hash. On python.org, each download link has a corresponding SHA-256 hash listed next to it. Copy the full 64-character string carefully — a single wrong character means the verification will always fail even for a good download.

Step 3: Open a terminal or PowerShell and run the hash command for your platform (see commands below).

Step 4: Compare every character of the computed hash against the published hash. They must match completely — all 64 characters of SHA-256, not just the first or last few. One character difference means the files differ.

Step 5: If they match, proceed. If they do not match, delete the downloaded file immediately and do not open or execute it under any circumstances.

Platform Commands: macOS

# SHA-256 — standard choice for all modern software
shasum -a 256 /path/to/downloaded-file.pkg

# SHA-512 — stronger, same family
shasum -a 512 /path/to/downloaded-file.pkg

# MD5 — older software and package managers
md5 /path/to/downloaded-file.pkg

# One-command verification (pass/fail output)
echo "expectedhashvalue  downloaded-file.pkg" | shasum -a 256 --check
# Output on match:   downloaded-file.pkg: OK
# Output on failure: downloaded-file.pkg: FAILED

# Verify multiple files against a SHA256SUMS file
shasum -a 256 --check SHA256SUMS.txt

Platform Commands: Linux

# SHA-256
sha256sum /path/to/downloaded-file.tar.gz

# SHA-512
sha512sum /path/to/downloaded-file.tar.gz

# MD5
md5sum /path/to/downloaded-file.tar.gz

# Single-file verification against a known hash
echo "expectedhashvalue  filename.tar.gz" | sha256sum --check
# OK = match, FAILED = mismatch

# Multi-file batch verification (common for Linux distros)
sha256sum --check SHA256SUMS.txt
# Verifies every file listed in the checksums file; reports each result

# Check only one file from a multi-file checksum file
grep "specific-file.tar.gz" SHA256SUMS.txt | sha256sum --check

Platform Commands: Windows (PowerShell)

# SHA-256
Get-FileHash .\downloaded-installer.exe -Algorithm SHA256

# SHA-512
Get-FileHash .\downloaded-installer.exe -Algorithm SHA512

# MD5
Get-FileHash .\downloaded-installer.exe -Algorithm MD5

# Compare hash directly in one command
$expected = "paste_expected_64_char_hash_here"
$actual = (Get-FileHash .\downloaded-installer.exe -Algorithm SHA256).Hash
if ($actual -eq $expected.ToUpper()) {
    Write-Host "VERIFIED: File hash matches" -ForegroundColor Green
} else {
    Write-Host "FAILED: Hash mismatch - do not use this file" -ForegroundColor Red
    Write-Host "Expected: $expected"
    Write-Host "Computed: $actual"
}

Note: PowerShell's Get-FileHash outputs the hash in uppercase. Published hashes may be lowercase. Compare with .ToUpper() or .ToLower() on both sides to avoid false failures from case differences alone.

Python: Cross-Platform Hash Verification Script

Python's hashlib module provides SHA-256, SHA-512, MD5, and SHA-1 on all platforms with identical syntax. This makes Python the best choice for automation scripts, CI/CD pipelines, and verification tools that need to run on Windows, macOS, and Linux without platform-specific commands.

#!/usr/bin/env python3
"""
verify_hash.py — Cross-platform file hash verification
Usage: python3 verify_hash.py FILE EXPECTED_HASH [--algo sha256]
"""
import hashlib
import sys
import argparse

ALGORITHMS = {
    'sha256': hashlib.sha256,
    'sha512': hashlib.sha512,
    'md5': hashlib.md5,
    'sha1': hashlib.sha1,
}

def compute_hash(filepath: str, algorithm: str = 'sha256') -> str:
    """Compute hash of a file using streaming reads for large file support."""
    h = ALGORITHMS[algorithm]()
    chunk_size = 65536  # 64KB chunks — efficient for files of any size
    try:
        with open(filepath, 'rb') as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                h.update(chunk)
    except FileNotFoundError:
        print(f"ERROR: File not found: {filepath}", file=sys.stderr)
        sys.exit(2)
    except PermissionError:
        print(f"ERROR: Permission denied: {filepath}", file=sys.stderr)
        sys.exit(2)
    return h.hexdigest()

def verify(filepath: str, expected: str, algorithm: str = 'sha256') -> bool:
    """Verify file hash against expected value. Returns True on match."""
    actual = compute_hash(filepath, algorithm)
    expected_clean = expected.strip().lower()
    actual_clean = actual.lower()
    if actual_clean == expected_clean:
        print(f"VERIFIED ({algorithm.upper()}): {filepath}")
        return True
    else:
        print(f"FAILED ({algorithm.upper()}): {filepath}")
        print(f"  Expected: {expected_clean}")
        print(f"  Got:      {actual_clean}")
        return False

def main():
    parser = argparse.ArgumentParser(description='Verify file hash checksum')
    parser.add_argument('file', help='Path to file to verify')
    parser.add_argument('expected_hash', help='Expected hash value (hex)')
    parser.add_argument('--algo', default='sha256', choices=ALGORITHMS.keys(),
                        help='Hash algorithm (default: sha256)')
    args = parser.parse_args()
    success = verify(args.file, args.expected_hash, args.algo)
    sys.exit(0 if success else 1)

if __name__ == '__main__':
    main()

Verifying Without a Terminal: Online Method

ToolPry's Hash Generator computes SHA-256, SHA-512, MD5, and SHA-1 directly in your browser. Drag and drop any file onto the tool and the hash is computed locally using the Web Crypto API — your file is never uploaded to any server. This is safe even for sensitive or confidential files, because the computation happens entirely within your browser tab. Compare the computed hash against the publisher's published value to complete verification.

Hash Algorithms: Which to Use and Why

SHA-256 — Use this for everything new

SHA-256 is the current industry standard for file integrity verification. No practical collision attack exists — the output space is 2²⁵⁶ possible values, and no two distinct inputs have ever been found to produce the same output. All actively maintained software distributions publish SHA-256 checksums. It produces 64 hexadecimal characters. If a publisher offers multiple algorithms, choose SHA-256 as your primary verification method.

SHA-512 — Stronger option from the same family

SHA-512 produces 128 hexadecimal characters and provides a larger security margin. On 64-bit hardware, it processes data in 64-bit words and can be faster than SHA-256 for large files despite the longer output. Use it when a publisher provides it and you want maximum confidence. Less universally published than SHA-256 but increasingly common for security-critical software distributions.

MD5 — Legacy only, avoid for security purposes

MD5 produces 32 hexadecimal characters. The algorithm is cryptographically broken — practical collision attacks have been demonstrated in which an attacker constructs two different files with identical MD5 hashes. For detecting accidental file corruption (a partial download, network error, or storage fault), MD5 remains functional — a randomly corrupted file will produce a different MD5 with overwhelming probability. But for detecting intentional tampering, MD5 cannot be trusted. An attacker who can create a collision could substitute a malicious file that matches the published MD5. Use MD5 only when no stronger alternative is published.

SHA-1 — Deprecated, do not use

SHA-1 is deprecated for security use following Google's SHAttered project in 2017, which produced two different PDF files with identical SHA-1 hashes. Software distributions that publish only SHA-1 checksums should be treated as using outdated security practices. Some tools use SHA-1 internally for non-security purposes (Git uses it for object addressing, though migration to SHA-256 is underway), but do not use it for new file integrity verification systems.

What to Do When Hashes Do Not Match

A hash mismatch is a serious event. The file you downloaded is definitively not identical to the file the publisher hashed. Do not open it, execute it, install it, or trust it in any way until you understand why the hashes differ.

Start with the most common benign causes: The download was incomplete — re-download the file and verify the size matches what the publisher lists. You compared against the wrong version's hash — double-check that the hash you copied corresponds to the exact file name and version you downloaded. There is extra whitespace or invisible characters in the hash you pasted — copy it again directly from the source. You are computing the wrong algorithm — ensure you are using SHA-256 if the publisher published a SHA-256 hash.

If the mismatch persists after a fresh download: Report the issue to the publisher through their official contact channel (not through a link on the page where you found the mismatch, as that page may itself be compromised). Do not install or use the downloaded file. Check security advisory mailing lists for the software project — if their distribution infrastructure was compromised, they may have already posted a notice.

For critical infrastructure software: Additionally verify a PGP signature from the publisher's key. A PGP-signed checksum file, obtained from the publisher's official key published on a separate trusted channel, proves that the same private key holder who historically signed legitimate releases also signed this hash. An attacker who compromised only the distribution server typically cannot also forge the PGP signature.

Publishing Checksums for Your Own Software

#!/usr/bin/env python3
"""
generate_checksums.py — Generate SHA-256 checksums for all release files
Creates SHA256SUMS.txt in the format compatible with sha256sum --check
"""
import hashlib
import glob
import os
import sys

RELEASE_DIR = sys.argv[1] if len(sys.argv) > 1 else 'dist'
OUTPUT_FILE = os.path.join(RELEASE_DIR, 'SHA256SUMS.txt')

lines = []
for filepath in sorted(glob.glob(os.path.join(RELEASE_DIR, '*'))):
    if not os.path.isfile(filepath) or filepath.endswith('SHA256SUMS.txt'):
        continue
    sha256 = hashlib.sha256()
    with open(filepath, 'rb') as f:
        for chunk in iter(lambda: f.read(65536), b''):
            sha256.update(chunk)
    filename = os.path.basename(filepath)
    line = f"{sha256.hexdigest()}  {filename}"
    lines.append(line)
    print(line)

with open(OUTPUT_FILE, 'w') as f:
    f.write('\n'.join(lines) + '\n')

print(f"\nChecksums written to {OUTPUT_FILE}")
print("Users can verify with: sha256sum --check SHA256SUMS.txt")
# Add this to your CI/CD pipeline (GitHub Actions example)
# .github/workflows/release.yml
- name: Generate checksums
  run: python3 scripts/generate_checksums.py dist/

- name: Upload release with checksums
  uses: actions/upload-artifact@v4
  with:
    name: release
    path: |
      dist/*.tar.gz
      dist/*.zip
      dist/SHA256SUMS.txt

Hash Verification in Automation and Security Pipelines

In DevOps and security engineering, hash verification is a standard step in dependency installation, artifact promotion, and deployment pipelines. Any pipeline that downloads external dependencies — package installers, binary tools, container base images — should verify their hashes before use. This provides a defence-in-depth layer against supply chain attacks, where an attacker compromises a dependency and modifies it to include malicious code.

#!/usr/bin/env bash
# download_and_verify.sh — Download a file and verify its SHA-256 hash

FILE_URL="https://example.com/software-v1.0.tar.gz"
EXPECTED_HASH="a1b2c3d4e5f6..."  # 64 hex chars from publisher
OUTPUT_FILE="software-v1.0.tar.gz"

echo "Downloading $FILE_URL..."
curl -fsSL -o "$OUTPUT_FILE" "$FILE_URL"

echo "Verifying SHA-256..."
ACTUAL=$(sha256sum "$OUTPUT_FILE" | awk '{print $1}')

if [ "$ACTUAL" = "$EXPECTED_HASH" ]; then
    echo "VERIFIED: Hash matches"
    exit 0
else
    echo "FAILED: Hash mismatch"
    echo "  Expected: $EXPECTED_HASH"
    echo "  Got:      $ACTUAL"
    rm -f "$OUTPUT_FILE"  # delete the suspect file
    exit 1
fi

Frequently Asked Questions

Can two different files have the same SHA-256 hash?

Theoretically yes — any finite hash function with fixed output length must have collisions among the infinite possible inputs. But no practical collision for SHA-256 has ever been found. The output space (2²⁵⁶ possible values) is so vast that finding one intentionally would require more computational work than all computers on Earth combined could perform in millions of years. For all practical purposes, if the SHA-256 hash matches, the files are identical.

Should I verify hashes even when downloading over HTTPS?

Yes, for security-sensitive software. HTTPS prevents an attacker positioned between you and the server from modifying the file in transit. But HTTPS does not protect against: the distribution server itself being compromised and serving a malicious file, you accidentally navigating to a phishing site that looks like the official one, or a malicious insider at the distribution provider. Hash verification adds a layer that catches these attacks — if the hash from a trusted source does not match your downloaded file, something went wrong regardless of whether HTTPS was used.

What if the publisher provides only an MD5 hash?

Verify it anyway — MD5 still catches random corruption reliably, which is the most common reason downloads fail. Then also consider: does this software project's use of MD5-only checksums reflect outdated security practices in the project overall? For mission-critical security software, this would be a yellow flag worth investigating. For a legacy database driver or an older open-source tool, it reflects the age of the project more than a current security failure. Use context to judge the risk level.

How do I verify the hash of a file inside a zip or tar.gz archive?

Hash the archive file itself before extracting it, then compare against the published hash of the archive. Some publishers also provide separate hashes for individual files inside archives. If a hash is published for a specific file inside an archive, extract that file after verifying the archive, then hash the extracted file separately. Hashing files before extraction is the most common and practical approach for most use cases.

Can I use hash verification to detect file tampering in my own application?

Yes — this is a common pattern for detecting unauthorised modification of application files, license files, or configuration data. Store the SHA-256 hash of each critical file at installation time in a secure location (a database with restricted access, a signed manifest, or a remote verification service). On startup or on a schedule, recompute the hash of each file and compare. Any discrepancy indicates the file has been modified outside normal update channels. This does not replace proper access controls, but adds a detection layer for compromise scenarios where access controls were bypassed.