Checksum Verifier — TypeScript source
Drag and drop a file to compute its MD5, SHA-1, SHA-256, and SHA-512 checksums. Paste an expected hash to verify integrity - detect tampered or corrupted downloads instantly. Runs entirely in your browser.
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
/**
* Checksum Verifier - pure logic.
*
* MD5 is implemented from scratch in pure JS (RFC 1321) with an incremental
* update/finalize interface, because the Web Crypto API does not support MD5.
* SHA-1 / SHA-256 / SHA-512 use the native Web Crypto `subtle.digest`.
*/
export interface ChecksumResult {
md5: string;
sha1: string;
sha256: string;
sha512: string;
}
export type HashAlgorithm = 'md5' | 'sha1' | 'sha256' | 'sha512';
// ---------------------------------------------------------------------------
// MD5 (RFC 1321)
// ---------------------------------------------------------------------------
// Per-round shift amounts (RFC 1321, section 3.4).
const MD5_S = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
];
// T[i] = floor(2^32 * |sin(i + 1)|) - the RFC 1321 sine table. IEEE-754
// doubles make this deterministic on every platform.
const MD5_K = new Int32Array(64).map((_, i) =>
Math.floor(Math.abs(Math.sin(i + 1)) * 4294967296),
);
export interface MD5Hasher {
/** Absorb bytes. Call repeatedly for chunked input (no length limit). */
update(data: Uint8Array): void;
/** Pad, append the 64-bit length, and return the lowercase hex digest. */
digestHex(): string;
}
/** Create an incremental MD5 hasher (RFC 1321, MD5 is only for integrity checks - not security). */
export function createMD5(): MD5Hasher {
let a0 = 0x67452301 | 0;
let b0 = 0xefcdab89 | 0;
let c0 = 0x98badcfe | 0;
let d0 = 0x10325476 | 0;
let total = 0;
const buf = new Uint8Array(64);
let buffered = 0;
function transform(block: Uint8Array, start: number): void {
const m = new Int32Array(16);
for (let i = 0; i < 16; i++) {
const o = start + i * 4;
m[i] = block[o] | (block[o + 1] << 8) | (block[o + 2] << 16) | (block[o + 3] << 24);
}
let a = a0;
let b = b0;
let c = c0;
let d = d0;
for (let i = 0; i < 64; i++) {
let f: number;
let g: number;
if (i < 16) {
f = (b & c) | (~b & d);
g = i;
} else if (i < 32) {
f = (d & b) | (~d & c);
g = (5 * i + 1) % 16;
} else if (i < 48) {
f = b ^ c ^ d;
g = (3 * i + 5) % 16;
} else {
f = c ^ (b | ~d);
g = (7 * i) % 16;
}
const sum = (f + a + MD5_K[i] + m[g]) | 0;
const rot = MD5_S[i];
const tmp = d;
d = c;
c = b;
b = (b + ((sum << rot) | (sum >>> (32 - rot)))) | 0;
a = tmp;
}
a0 = (a0 + a) | 0;
b0 = (b0 + b) | 0;
c0 = (c0 + c) | 0;
d0 = (d0 + d) | 0;
}
function update(data: Uint8Array): void {
total += data.length;
let pos = 0;
if (buffered > 0) {
const take = Math.min(64 - buffered, data.length);
buf.set(data.subarray(0, take), buffered);
buffered += take;
pos = take;
if (buffered === 64) {
transform(buf, 0);
buffered = 0;
}
}
while (pos + 64 <= data.length) {
transform(data, pos);
pos += 64;
}
if (pos < data.length) {
buf.set(data.subarray(pos), buffered);
buffered += data.length - pos;
}
}
function digestHex(): string {
// Capture the message length BEFORE padding updates `total`.
const bitLenLo = (total * 8) >>> 0;
const bitLenHi = Math.floor(total / 536870912) >>> 0; // total * 8 / 2^32
const padLen = buffered < 56 ? 56 - buffered : 120 - buffered;
const pad = new Uint8Array(padLen);
pad[0] = 0x80;
update(pad);
const lenBytes = new Uint8Array(8);
lenBytes[0] = bitLenLo & 0xff;
lenBytes[1] = (bitLenLo >>> 8) & 0xff;
lenBytes[2] = (bitLenLo >>> 16) & 0xff;
lenBytes[3] = (bitLenLo >>> 24) & 0xff;
lenBytes[4] = bitLenHi & 0xff;
lenBytes[5] = (bitLenHi >>> 8) & 0xff;
lenBytes[6] = (bitLenHi >>> 16) & 0xff;
lenBytes[7] = (bitLenHi >>> 24) & 0xff;
update(lenBytes); // buffered is exactly 56, so this fills the final block
function wordLE(n: number): string {
let s = '';
for (let i = 0; i < 4; i++) s += ((n >>> (8 * i)) & 0xff).toString(16).padStart(2, '0');
return s;
}
return wordLE(a0) + wordLE(b0) + wordLE(c0) + wordLE(d0);
}
return { update, digestHex };
}
/** Synchronous one-shot MD5 of an ArrayBuffer, as lowercase hex. */
export function md5(data: ArrayBuffer): string {
const hasher = createMD5();
hasher.update(new Uint8Array(data));
return hasher.digestHex();
}
// ---------------------------------------------------------------------------
// SHA-1 / SHA-256 / SHA-512 via Web Crypto
// ---------------------------------------------------------------------------
function getSubtle(): SubtleCrypto {
const subtle = globalThis.crypto?.subtle;
if (!subtle) {
throw new Error('Web Crypto (crypto.subtle) is not available in this environment');
}
return subtle;
}
function toHex(bytes: Uint8Array): string {
let s = '';
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, '0');
return s;
}
/** Digest a buffer with Web Crypto and return lowercase hex. */
export async function shaHex(
algorithm: 'SHA-1' | 'SHA-256' | 'SHA-512',
data: ArrayBuffer | Uint8Array,
): Promise<string> {
const view = data instanceof Uint8Array ? data : new Uint8Array(data);
const digest = await getSubtle().digest(algorithm, view);
return toHex(new Uint8Array(digest));
}
/** Compute all four checksums of a buffer (MD5 pure JS + 3 Web Crypto digests). */
export async function computeChecksums(data: ArrayBuffer): Promise<ChecksumResult> {
const [sha1, sha256, sha512] = await Promise.all([
shaHex('SHA-1', data),
shaHex('SHA-256', data),
shaHex('SHA-512', data),
]);
return { md5: md5(data), sha1, sha256, sha512 };
}
// ---------------------------------------------------------------------------
// Verification helpers
// ---------------------------------------------------------------------------
const LENGTH_TO_ALGO: Record<string, HashAlgorithm> = {
'32': 'md5',
'40': 'sha1',
'64': 'sha256',
'128': 'sha512',
};
/** Normalize a pasted hash: drop whitespace and `:` grouping, lowercase. */
function normalizeHash(hash: string): string {
return hash.trim().replace(/[\s:]/g, '').toLowerCase();
}
/** Detect the algorithm from the hex length: 32=MD5, 40=SHA-1, 64=SHA-256, 128=SHA-512. */
export function detectHashAlgorithm(hash: string): HashAlgorithm | null {
const h = normalizeHash(hash);
if (!/^[0-9a-f]+$/.test(h)) return null;
return LENGTH_TO_ALGO[String(h.length)] ?? null;
}
/**
* Compare an expected hash against a computed result. Returns null when the
* expected string is not a recognizable hex hash of a supported length.
*/
export function verifyChecksum(
expected: string,
result: ChecksumResult,
): { algorithm: HashAlgorithm; match: boolean } | null {
const algorithm = detectHashAlgorithm(expected);
if (!algorithm) return null;
return { algorithm, match: normalizeHash(expected) === result[algorithm] };
}
Also available in 8 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →