Skip to main content

SHA256 Verification Patterns

SHA256 Verification Patterns

How to Use This Skill

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

Expert skill for implementing secure checksum verification across platforms to ensure binary integrity and prevent supply chain attacks.

When to Use

Use this skill when:

  • Distributing binaries via CDN (installers, CLI tools)
  • Implementing postinstall verification in npm packages
  • Creating native installers with integrity checks
  • Building CI/CD pipelines that publish artifacts
  • Implementing secure auto-update mechanisms

Don't use this skill when:

  • Verifying file integrity within a trusted system
  • Performance-critical hot paths (SHA256 is CPU-intensive)
  • Already using code signing (signing includes hashing)

Core Concepts

SHA256 Properties

  • Deterministic: Same input always produces same hash
  • Fixed size: Always 64 hex characters (256 bits)
  • Collision resistant: Infeasible to find two inputs with same hash
  • One-way: Cannot reverse hash to get original content

Security Requirements

  1. HTTPS only - Never fetch manifests or binaries over HTTP
  2. Pin manifest URL - Don't follow redirects to untrusted sources
  3. Verify before execute - Always verify checksum before running/installing
  4. Fail closed - Abort on any verification failure

Checksum Generation

Bash (sha256sum / shasum)

#!/usr/bin/env bash
set -euo pipefail

# Generate SHA256 checksum
generate_checksum() {
local file="$1"
local checksum

if command -v sha256sum &>/dev/null; then
# Linux (coreutils)
checksum=$(sha256sum "$file" | cut -d' ' -f1)
elif command -v shasum &>/dev/null; then
# macOS / BSD
checksum=$(shasum -a 256 "$file" | cut -d' ' -f1)
else
echo "ERROR: No SHA256 tool available" >&2
return 1
fi

echo "$checksum"
}

# Generate checksums for all files in directory
generate_manifest() {
local dir="$1"
local manifest="$2"

echo "{" > "$manifest"
echo ' "files": {' >> "$manifest"

local first=true
for file in "$dir"/*; do
[[ -f "$file" ]] || continue

local name=$(basename "$file")
local checksum=$(generate_checksum "$file")
local size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file")

if [[ "$first" == "true" ]]; then
first=false
else
echo "," >> "$manifest"
fi

printf ' "%s": {"sha256": "%s", "size": %d}' "$name" "$checksum" "$size" >> "$manifest"
done

echo "" >> "$manifest"
echo " }" >> "$manifest"
echo "}" >> "$manifest"
}

# Usage
generate_checksum "binary-file"
generate_manifest "./dist" "manifest.json"

Python

#!/usr/bin/env python3
"""SHA256 checksum generation utilities."""

import hashlib
import json
from pathlib import Path
from typing import Dict, Any


def generate_checksum(file_path: Path, chunk_size: int = 8192) -> str:
"""
Generate SHA256 checksum for a file.

Args:
file_path: Path to file
chunk_size: Read buffer size (default 8KB)

Returns:
Lowercase hex digest (64 characters)
"""
sha256 = hashlib.sha256()

with open(file_path, 'rb') as f:
while chunk := f.read(chunk_size):
sha256.update(chunk)

return sha256.hexdigest()


def generate_manifest(directory: Path, output: Path) -> Dict[str, Any]:
"""
Generate manifest with checksums for all files in directory.

Args:
directory: Source directory containing files
output: Output path for manifest.json

Returns:
Manifest dictionary
"""
manifest = {
"version": "1.0.0",
"generated": datetime.utcnow().isoformat() + "Z",
"files": {}
}

for file_path in directory.iterdir():
if not file_path.is_file():
continue

manifest["files"][file_path.name] = {
"sha256": generate_checksum(file_path),
"size": file_path.stat().st_size
}

with open(output, 'w') as f:
json.dump(manifest, f, indent=2)

return manifest


if __name__ == "__main__":
import sys
from datetime import datetime

if len(sys.argv) < 2:
print("Usage: python generate_checksums.py <directory> [manifest.json]")
sys.exit(1)

directory = Path(sys.argv[1])
output = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("manifest.json")

manifest = generate_manifest(directory, output)
print(f"Generated manifest with {len(manifest['files'])} files")

Node.js

// generate-checksums.js
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

/**
* Generate SHA256 checksum for a file
* @param {string} filePath - Path to file
* @returns {Promise<string>} Lowercase hex digest
*/
async function generateChecksum(filePath) {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256');
const stream = fs.createReadStream(filePath);

stream.on('data', (data) => hash.update(data));
stream.on('end', () => resolve(hash.digest('hex')));
stream.on('error', reject);
});
}

/**
* Generate manifest for all files in directory
* @param {string} directory - Source directory
* @param {string} output - Output manifest path
*/
async function generateManifest(directory, output) {
const manifest = {
version: '1.0.0',
generated: new Date().toISOString(),
files: {}
};

const files = fs.readdirSync(directory);

for (const file of files) {
const filePath = path.join(directory, file);
const stat = fs.statSync(filePath);

if (!stat.isFile()) continue;

manifest.files[file] = {
sha256: await generateChecksum(filePath),
size: stat.size
};
}

fs.writeFileSync(output, JSON.stringify(manifest, null, 2));
return manifest;
}

module.exports = { generateChecksum, generateManifest };

Checksum Verification

Bash

#!/usr/bin/env bash
set -euo pipefail

# Verify SHA256 checksum
verify_checksum() {
local file="$1"
local expected="$2"
local actual

if command -v sha256sum &>/dev/null; then
actual=$(sha256sum "$file" | cut -d' ' -f1)
elif command -v shasum &>/dev/null; then
actual=$(shasum -a 256 "$file" | cut -d' ' -f1)
else
echo "ERROR: No SHA256 tool available" >&2
return 1
fi

# Case-insensitive comparison
if [[ "${actual,,}" != "${expected,,}" ]]; then
echo "ERROR: Checksum verification failed!" >&2
echo " Expected: $expected" >&2
echo " Actual: $actual" >&2
return 1
fi

echo "Checksum verified successfully"
return 0
}

# Verify file against manifest
verify_from_manifest() {
local file="$1"
local manifest="$2"
local filename=$(basename "$file")

# Extract expected checksum from JSON manifest (requires jq)
local expected=$(jq -r ".files[\"$filename\"].sha256" "$manifest")

if [[ "$expected" == "null" || -z "$expected" ]]; then
echo "ERROR: File not found in manifest: $filename" >&2
return 1
fi

verify_checksum "$file" "$expected"
}

# Usage
verify_checksum "downloaded-binary" "abc123..."
verify_from_manifest "downloaded-binary" "manifest.json"

Python

#!/usr/bin/env python3
"""SHA256 checksum verification utilities."""

import hashlib
import json
import sys
from pathlib import Path
from typing import Optional


class ChecksumError(Exception):
"""Raised when checksum verification fails."""
pass


def verify_checksum(
file_path: Path,
expected: str,
chunk_size: int = 8192
) -> bool:
"""
Verify SHA256 checksum of a file.

Args:
file_path: Path to file to verify
expected: Expected SHA256 hex digest
chunk_size: Read buffer size

Returns:
True if checksum matches

Raises:
ChecksumError: If checksum doesn't match
FileNotFoundError: If file doesn't exist
"""
sha256 = hashlib.sha256()

with open(file_path, 'rb') as f:
while chunk := f.read(chunk_size):
sha256.update(chunk)

actual = sha256.hexdigest()

# Case-insensitive comparison
if actual.lower() != expected.lower():
raise ChecksumError(
f"Checksum verification failed!\n"
f" Expected: {expected}\n"
f" Actual: {actual}"
)

return True


def verify_from_manifest(
file_path: Path,
manifest_path: Path
) -> bool:
"""
Verify file checksum against manifest.

Args:
file_path: Path to file to verify
manifest_path: Path to manifest.json

Returns:
True if checksum matches

Raises:
ChecksumError: If checksum doesn't match
KeyError: If file not in manifest
"""
with open(manifest_path) as f:
manifest = json.load(f)

filename = file_path.name

if filename not in manifest.get('files', {}):
raise KeyError(f"File not found in manifest: {filename}")

expected = manifest['files'][filename]['sha256']
return verify_checksum(file_path, expected)


if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python verify_checksums.py <file> <expected|manifest.json>")
sys.exit(1)

file_path = Path(sys.argv[1])
arg2 = sys.argv[2]

try:
if arg2.endswith('.json'):
verify_from_manifest(file_path, Path(arg2))
else:
verify_checksum(file_path, arg2)
print(f"✓ Checksum verified: {file_path.name}")
except (ChecksumError, KeyError) as e:
print(f"✗ {e}", file=sys.stderr)
sys.exit(1)

PowerShell

#Requires -Version 5.1

function Test-FileChecksum {
<#
.SYNOPSIS
Verify SHA256 checksum of a file.

.PARAMETER FilePath
Path to file to verify.

.PARAMETER Expected
Expected SHA256 hash (64 hex characters).

.EXAMPLE
Test-FileChecksum -FilePath "binary.exe" -Expected "abc123..."
#>
param(
[Parameter(Mandatory)]
[string]$FilePath,

[Parameter(Mandatory)]
[string]$Expected
)

if (-not (Test-Path $FilePath)) {
throw "File not found: $FilePath"
}

$actual = (Get-FileHash -Path $FilePath -Algorithm SHA256).Hash

if ($actual.ToLower() -ne $Expected.ToLower()) {
throw @"
Checksum verification failed!
Expected: $Expected
Actual: $actual
"@
}

Write-Host "Checksum verified successfully" -ForegroundColor Green
return $true
}

function Test-FileFromManifest {
<#
.SYNOPSIS
Verify file checksum against manifest.json.

.PARAMETER FilePath
Path to file to verify.

.PARAMETER ManifestPath
Path to manifest.json.
#>
param(
[Parameter(Mandatory)]
[string]$FilePath,

[Parameter(Mandatory)]
[string]$ManifestPath
)

$manifest = Get-Content $ManifestPath | ConvertFrom-Json
$filename = Split-Path $FilePath -Leaf

if (-not $manifest.files.$filename) {
throw "File not found in manifest: $filename"
}

$expected = $manifest.files.$filename.sha256
Test-FileChecksum -FilePath $FilePath -Expected $expected
}

Manifest File Format

Standard Manifest (manifest.json)

{
"name": "@scope/package-name",
"version": "1.0.0",
"generated": "2025-12-08T00:00:00Z",
"algorithm": "sha256",
"files": {
"package-darwin-x64.tar.gz": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 12345678,
"url": "https://dist.example.com/v1.0.0/package-darwin-x64.tar.gz"
},
"package-darwin-arm64.tar.gz": {
"sha256": "abc123...",
"size": 12345678,
"url": "https://dist.example.com/v1.0.0/package-darwin-arm64.tar.gz"
}
}
}

CHECKSUMS.txt Format (Alternative)

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855  package-darwin-x64.tar.gz
abc123def456... package-darwin-arm64.tar.gz

Verify with: sha256sum -c CHECKSUMS.txt


Security Best Practices

  1. Always verify before extraction/execution

    # Good
    verify_checksum "$file" "$expected" && tar -xzf "$file"

    # Bad - extracts before verification!
    tar -xzf "$file" && verify_checksum "$file" "$expected"
  2. Use constant-time comparison (for cryptographic contexts)

    import hmac
    hmac.compare_digest(actual.lower(), expected.lower())
  3. Delete file on verification failure

    if ! verify_checksum "$file" "$expected"; then
    rm -f "$file"
    exit 1
    fi
  4. Log verification attempts (for audit trails)

    logger.info(f"Verifying {filename}: expected={expected[:16]}...")
  5. Pin manifest sources (don't follow redirects)

    curl --max-redirs 0 "$MANIFEST_URL"

  • PATTERNS.md - Common verification patterns
  • native-installer-builder agent - Uses these patterns in install scripts
  • npm-optionaldeps-pattern skill - Postinstall verification

Success Output

When successful, this skill MUST output:

✅ SKILL COMPLETE: sha256-verification

Completed:
- [x] Checksum generation complete
- [x] Manifest file created
- [x] Verification successful
- [x] All checksums validated

Outputs:
- Manifest: manifest.json (or CHECKSUMS.txt)
- Files verified: {count}
- Algorithm: SHA256

Verification Results:
- Total files: {count}
- Verified: {count}
- Failed: 0
- Checksum format: 64-character hex digest

Completion Checklist

Before marking this skill as complete, verify:

  • Checksums generated for all files
  • Manifest file created (JSON or txt format)
  • All checksums are 64 hex characters
  • Verification passes for all files
  • HTTPS-only manifest URL (no HTTP)
  • Manifest URL does not follow redirects
  • Verification occurs BEFORE file extraction/execution
  • Files deleted on verification failure
  • Constant-time comparison used (cryptographic contexts)
  • Verification attempts logged

Failure Indicators

This skill has FAILED if:

  • ❌ Checksum mismatch detected
  • ❌ File not found in manifest
  • ❌ Manifest downloaded over HTTP (insecure)
  • ❌ File executed before verification
  • ❌ Verification tool not available (sha256sum/shasum)
  • ❌ Manifest JSON parse error
  • ❌ Checksum not 64 hex characters
  • ❌ File deleted before verification

When NOT to Use

Do NOT use this skill when:

  • Verifying file integrity within a trusted system (use simpler checksums like MD5)
  • Performance-critical hot paths (SHA256 is CPU-intensive, use faster alternatives)
  • Already using code signing (signing includes hashing, no need for separate SHA256)
  • Files are tiny (< 1KB) and verification overhead is disproportionate
  • Working in isolated environment with no network access (cannot fetch manifest)
  • Need to verify streaming data (use incremental hashing instead)
  • Verifying content in-memory (not file-based)

Anti-Patterns (Avoid)

Anti-PatternProblemSolution
Fetching manifest over HTTPMan-in-the-middle attacksAlways use HTTPS for manifest URLs
Following redirectsManifest source tamperingUse --max-redirs 0 in curl
Executing before verificationMalware executionAlways verify BEFORE extraction/execution
Not deleting on failureCorrupted files remainDelete file immediately on checksum mismatch
Case-sensitive comparisonFalse negativesUse lowercase comparison
No audit trailNo verification proofLog all verification attempts
Hardcoded checksumsDifficult to updateUse external manifest.json
Not checking manifest integrityTampered manifestSign manifest or use trusted CDN
Using timing-vulnerable comparisonSide-channel attacksUse hmac.compare_digest() in Python
Skipping size validationIncomplete downloadsVerify file size matches manifest

Principles

This skill embodies:

  • #5 Eliminate Ambiguity - Clear pass/fail verification with explicit checksums
  • #8 No Assumptions - Verify every file against known-good checksums
  • #9 Security by Design - HTTPS-only, verify-before-execute, fail-closed
  • #10 Fail Closed - Abort and delete on any verification failure
  • #12 Measure, Don't Guess - Cryptographic proof of file integrity
  • #15 Separation of Concerns - Checksum generation separate from verification

Full Standard: CODITECT-STANDARD-AUTOMATION.md