Skip to content

HMAC Generator — Zig source

Generate a keyed-hash HMAC (SHA-1/256/384/512) for a message and secret. Runs entirely in your browser via Web Crypto, with a shareable link to your exact input.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

//! hmac-generator — RFC 2104 keyed-hash HMAC of a UTF-8 message, hex output.
//!
//! Language: Zig 0.13 (standard library only)
//! Source:   CosmoDev polyglot showcase port of the `hmac-generator` tool,
//!           ported from src/lib/hmac.ts (the canonical TypeScript lib).
//! License:  display source — part of CosmoDev's polyglot tool pages.
//!
//! Keyed HMAC via Zig's std.crypto — the standard library ships audited,
//! constant-time HMAC (RFC 2104) for the SHA-2 family as pre-built aliases,
//! and the generic `Hmac(Hash)` constructor covers SHA-1, so no third-party
//! packages are needed (the position the Rust port needs the RustCrypto
//! `hmac`/`sha1`/`sha2` crates for).
//!
//! Behavior mirrors the TypeScript reference: UTF-8 inputs, lowercase hex
//! output, SHA-256 by default, and rejection of empty secrets and unknown
//! algorithms. The empty-secret rejection keeps parity with SubtleCrypto,
//! which refuses a zero-length key that HMAC.create would otherwise accept.
//! SHA-1 is offered for legacy compatibility only; it is not
//! collision-resistant.

const std = @import("std");

/// The standard-library HMAC types, re-exported so callers never touch
/// std.crypto directly (mirrors the Rust port's type aliases).
pub const HmacSha1 = std.crypto.auth.hmac.Hmac(std.crypto.hash.Sha1);
pub const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256;
pub const HmacSha384 = std.crypto.auth.hmac.sha2.HmacSha384;
pub const HmacSha512 = std.crypto.auth.hmac.sha2.HmacSha512;

/// Canonical algorithm names. The spellings match the TypeScript union so
/// the same string works across every port.
pub const sha1_name = "SHA-1";
pub const sha256_name = "SHA-256"; // default algorithm
pub const sha384_name = "SHA-384";
pub const sha512_name = "SHA-512";

/// Errors returned by `hmacHex`. Kept as a concrete error set (not a string)
/// so callers can switch on the kind — the Zig standing of the Rust port's
/// HmacError enum.
pub const HmacError = error{UnknownAlgorithm, EmptySecret};

/// Longest hex tag, in bytes — size output buffers with this.
pub const max_hex_len = 128;

/// One-shot HMAC(`message`, `secret`) as lowercase hex, written into `out`.
///
/// Both arguments are the strings' UTF-8 byte sequences (a Zig slice is
/// untyped bytes, so a UTF-8 string needs no encoding step) — the exact
/// bytes a browser hands to crypto.subtle.sign. An empty `algorithm`
/// selects SHA-256 — the optional-parameter default the TypeScript reference
/// declares. An empty `secret` returns `error.EmptySecret`; an unknown
/// algorithm name returns `error.UnknownAlgorithm`. `out` must be at least
/// `max_hex_len` bytes; the written slice is returned so callers get the
/// exact length without computing it.
pub fn hmacHex(out: []u8, message: []const u8, secret: []const u8, algorithm: []const u8) HmacError![]const u8 {
    const name = if (algorithm.len == 0) sha256_name else algorithm;

    if (secret.len == 0)
        return HmacError.EmptySecret;

    if (std.mem.eql(u8, name, sha1_name))
        return hexOf(HmacSha1, out, message, secret);
    if (std.mem.eql(u8, name, sha256_name))
        return hexOf(HmacSha256, out, message, secret);
    if (std.mem.eql(u8, name, sha384_name))
        return hexOf(HmacSha384, out, message, secret);
    if (std.mem.eql(u8, name, sha512_name))
        return hexOf(HmacSha512, out, message, secret);
    return HmacError.UnknownAlgorithm;
}

/// Generic one-shot: HMAC `message` under `secret` with the std.crypto type
/// `H` and write its lowercase hex into `out`. `H.create` fixes the tag
/// length at compile time (H.mac_length); bytesToHex returns a fixed
/// [2*N]u8 array, which is why the copy goes through a comptime-sized
/// temporary.
fn hexOf(comptime H: type, out: []u8, message: []const u8, secret: []const u8) []const u8 {
    var mac: [H.mac_length]u8 = undefined;
    H.create(&mac, message, secret);
    const hex = std.fmt.bytesToHex(mac, .lower);
    @memcpy(out[0..hex.len], &hex);
    return out[0..hex.len];
}

Also available in 13 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 →