Skip to content

HMAC Generator — C# 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 C# 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: C# 12 (.NET 8, 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 System.Security.Cryptography — the vetted, constant-time
// implementations .NET ships on every platform. The static HashData entry
// points (added in .NET 8) take the key first and compute a one-shot tag
// without the using/Dispose ceremony the older Create() pattern needed, and
// Convert.ToHexString supplies the lowercase hex the TypeScript reference
// produces. No NuGet packages are required.
//
// Behavior mirrors the TypeScript reference: UTF-8 inputs, lowercase hex
// output, SHA-256 by default, and rejection of empty secrets and unknown
// algorithms (ArgumentException — the C# standing of the Python port's
// ValueError and the two arms of the Rust port's HmacError enum). An empty
// secret is rejected because SubtleCrypto refuses a zero-length key;
// HMACSHA256.HashData itself would accept one, and matching the reference
// contract keeps the ports in parity. SHA-1 is offered for legacy
// compatibility only; it is not collision-resistant.

using System.Security.Cryptography;
using System.Text;

namespace CosmoDev.Hmac;

/// <summary>Pure logic of the CosmoDev `hmac-generator` tool.</summary>
public static class HmacTool
{
    /// <summary>Canonical algorithm names. The spellings match the TypeScript
    /// union so the same string works across every port.</summary>
    public const string Sha1 = "SHA-1";
    public const string Sha256 = "SHA-256"; // default algorithm
    public const string Sha384 = "SHA-384";
    public const string Sha512 = "SHA-512";

    /// <summary>The algorithm selected when a caller passes an empty name —
    /// the optional-parameter default the TypeScript reference declares.</summary>
    public const string DefaultAlgorithm = Sha256;

    /// <summary>
    /// Compute HMAC(<paramref name="message"/>, <paramref name="secret"/>)
    /// under the named algorithm and return it as lowercase hex.
    ///
    /// Both inputs are UTF-8 encoded before hashing, so the result is correct
    /// for arbitrary Unicode (emoji, accents, CJK). An empty
    /// <paramref name="algorithm"/> selects SHA-256; an empty
    /// <paramref name="secret"/> or an unknown algorithm name throws
    /// <see cref="ArgumentException"/>.
    /// </summary>
    public static string HmacHex(string message, string secret, string? algorithm = null)
    {
        string name = string.IsNullOrEmpty(algorithm) ? DefaultAlgorithm : algorithm;

        if (secret.Length == 0)
            throw new ArgumentException("HMAC secret must not be empty.", nameof(secret));

        // Both inputs are the exact byte sequences a browser hands to
        // crypto.subtle.sign with an HmacImportParams key.
        byte[] key = Encoding.UTF8.GetBytes(secret);
        byte[] data = Encoding.UTF8.GetBytes(message);

        // Each arm fixes the implementation type at compile time. The
        // HashData one-shots (key first, then data) allocate no HMAC state
        // and dispose nothing.
        byte[] tag = name switch
        {
            Sha1 => HMACSHA1.HashData(key, data),
            Sha256 => HMACSHA256.HashData(key, data),
            Sha384 => HMACSHA384.HashData(key, data),
            Sha512 => HMACSHA512.HashData(key, data),
            _ => throw new ArgumentException($"Unsupported HMAC algorithm: {name}", nameof(algorithm)),
        };

        // ToHexString is uppercase; the reference emits lowercase.
        return Convert.ToHexString(tag).ToLowerInvariant();
    }
}

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 →