Skip to content

Checksum Verifier — Zig 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 Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

//! checksum-verifier — MD5 / SHA-1 / SHA-256 / SHA-512 checksums + verification.
//!
//! Language: Zig 0.14 (standard library only)
//! Ported from: src/lib/checksum-verifier.ts (the canonical TypeScript implementation).
//! display source — part of CosmoDev's polyglot tool pages.
//!
//! The TS reference implements MD5 from scratch (RFC 1321) because the Web
//! Crypto API does not support it. Zig's std.crypto ships MD5 natively
//! (`std.crypto.hash.Md5`), so this port uses the standard implementations
//! for all four algorithms — same inputs, same lowercase-hex outputs.

const std = @import("std");

pub const Md5 = std.crypto.hash.Md5;
pub const Sha1 = std.crypto.hash.Sha1;
pub const Sha256 = std.crypto.hash.sha2.Sha256;
pub const Sha512 = std.crypto.hash.sha2.Sha512;

pub const HashAlgorithm = enum {
    md5,
    sha1,
    sha256,
    sha512,
};

pub const ChecksumResult = struct {
    md5: [32]u8,
    sha1: [40]u8,
    sha256: [64]u8,
    sha512: [128]u8,

    /// The lowercase-hex digest for a given algorithm.
    pub fn get(self: *const ChecksumResult, algorithm: HashAlgorithm) []const u8 {
        return switch (algorithm) {
            .md5 => &self.md5,
            .sha1 => &self.sha1,
            .sha256 => &self.sha256,
            .sha512 => &self.sha512,
        };
    }
};

/// Detect the algorithm from the hex length: 32=MD5, 40=SHA-1, 64=SHA-256,
/// 128=SHA-512. Returns null for non-hex input or an unrecognized length.
pub fn detectHashAlgorithm(hash: []const u8) ?HashAlgorithm {
    const h = normalizeHashScratch(hash);
    if (h.len == 0) return null;
    for (h) |c| {
        if (!std.ascii.isHex(c) and !(c >= 'a' and c <= 'f')) return null;
    }
    return switch (h.len) {
        32 => .md5,
        40 => .sha1,
        64 => .sha256,
        128 => .sha512,
        else => 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.
pub fn verifyChecksum(expected: []const u8, result: *const ChecksumResult) ?struct {
    algorithm: HashAlgorithm,
    match: bool,
} {
    const algorithm = detectHashAlgorithm(expected) orelse return null;
    var buf: [128]u8 = undefined;
    const h = normalizeHash(&buf, expected);
    return .{ .algorithm = algorithm, .match = std.mem.eql(u8, h, result.get(algorithm)) };
}

/// Normalize a pasted hash: drop whitespace and `:` grouping, lowercase.
/// Writes into `buf` (128 bytes covers the longest supported hash) and
/// returns the normalized slice.
pub fn normalizeHash(buf: []u8, hash: []const u8) []const u8 {
    var n: usize = 0;
    for (hash) |c| {
        if (c == ' ' or c == '\t' or c == '\r' or c == '\n' or c == ':') continue;
        buf[n] = std.ascii.toLower(c);
        n += 1;
        if (n == buf.len) break;
    }
    return buf[0..n];
}

/// Non-allocating variant for already-trimmed input (used internally).
fn normalizeHashScratch(hash: []const u8) []const u8 {
    // Only valid as a length/hex check source; trailing whitespace is rare in
    // detect paths, so trim both ends before measuring.
    return std.mem.trim(u8, hash, " \t\r\n");
}

/// One-shot MD5 of a buffer as lowercase hex (`out` must be 32 bytes).
pub fn md5Hex(out: *[32]u8, data: []const u8) void {
    var digest: [Md5.digest_length]u8 = undefined;
    Md5.hash(data, &digest, .{});
    @memcpy(out, &std.fmt.bytesToHex(digest, .lower));
}

/// One-shot SHA-1 as lowercase hex (`out` must be 40 bytes).
pub fn sha1Hex(out: *[40]u8, data: []const u8) void {
    var digest: [Sha1.digest_length]u8 = undefined;
    Sha1.hash(data, &digest, .{});
    @memcpy(out, &std.fmt.bytesToHex(digest, .lower));
}

/// One-shot SHA-256 as lowercase hex (`out` must be 64 bytes).
pub fn sha256Hex(out: *[64]u8, data: []const u8) void {
    var digest: [Sha256.digest_length]u8 = undefined;
    Sha256.hash(data, &digest, .{});
    @memcpy(out, &std.fmt.bytesToHex(digest, .lower));
}

/// One-shot SHA-512 as lowercase hex (`out` must be 128 bytes).
pub fn sha512Hex(out: *[128]u8, data: []const u8) void {
    var digest: [Sha512.digest_length]u8 = undefined;
    Sha512.hash(data, &digest, .{});
    @memcpy(out, &std.fmt.bytesToHex(digest, .lower));
}

/// Compute all four checksums of a buffer, as lowercase hex.
pub fn computeChecksums(data: []const u8) ChecksumResult {
    var r: ChecksumResult = undefined;
    md5Hex(&r.md5, data);
    sha1Hex(&r.sha1, data);
    sha256Hex(&r.sha256, data);
    sha512Hex(&r.sha512, data);
    return r;
}

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 →