SHA256 Verification Patterns
SHA256 Verification Patterns
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- 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
- HTTPS only - Never fetch manifests or binaries over HTTP
- Pin manifest URL - Don't follow redirects to untrusted sources
- Verify before execute - Always verify checksum before running/installing
- 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
-
Always verify before extraction/execution
# Good
verify_checksum "$file" "$expected" && tar -xzf "$file"
# Bad - extracts before verification!
tar -xzf "$file" && verify_checksum "$file" "$expected" -
Use constant-time comparison (for cryptographic contexts)
import hmac
hmac.compare_digest(actual.lower(), expected.lower()) -
Delete file on verification failure
if ! verify_checksum "$file" "$expected"; then
rm -f "$file"
exit 1
fi -
Log verification attempts (for audit trails)
logger.info(f"Verifying {filename}: expected={expected[:16]}...") -
Pin manifest sources (don't follow redirects)
curl --max-redirs 0 "$MANIFEST_URL"
Related Resources
- 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-Pattern | Problem | Solution |
|---|---|---|
| Fetching manifest over HTTP | Man-in-the-middle attacks | Always use HTTPS for manifest URLs |
| Following redirects | Manifest source tampering | Use --max-redirs 0 in curl |
| Executing before verification | Malware execution | Always verify BEFORE extraction/execution |
| Not deleting on failure | Corrupted files remain | Delete file immediately on checksum mismatch |
| Case-sensitive comparison | False negatives | Use lowercase comparison |
| No audit trail | No verification proof | Log all verification attempts |
| Hardcoded checksums | Difficult to update | Use external manifest.json |
| Not checking manifest integrity | Tampered manifest | Sign manifest or use trusted CDN |
| Using timing-vulnerable comparison | Side-channel attacks | Use hmac.compare_digest() in Python |
| Skipping size validation | Incomplete downloads | Verify 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