Skip to content

HMAC Generator — JavaScript 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 JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

// hmac-generator - JavaScript polyglot showcase port
//
// Language: JavaScript (Web Crypto API / SubtleCrypto)
// Ported from: src/lib/hmac.ts
// Display source - part of CosmoDev's polyglot tool pages.
//
// Keyed-hash HMAC via the Web Crypto API. Pure logic - no DOM, no React.
// Deterministic for a given (message, secret, algorithm). Async only because
// SubtleCrypto is; requires a secure context (https or localhost).
//
// This is the same WebCrypto surface as the canonical TypeScript implementation,
// shown here without type annotations. Behavior matches exactly: UTF-8 inputs,
// lowercase hex output, SHA-256 by default, and rejection of empty secrets and
// unsupported algorithms.

// Supported HMAC hash algorithms. These spellings are SubtleCrypto's own
// algorithm names and are shared verbatim across every language port.
const HMAC_ALGORITHMS = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512'];

/**
 * Lowercase hexadecimal encoding of an ArrayBuffer.
 *
 * Spreading the byte view into a plain array lets map/join build one string
 * with no trailing separator; each byte is zero-padded to two hex digits.
 */
function toHex(buf) {
  return Array.from(new Uint8Array(buf))
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');
}

/**
 * Compute the HMAC of `message` under `secret` and return it as lowercase hex.
 *
 * Both inputs are UTF-8 encoded via TextEncoder. SubtleCrypto performs the
 * actual HMAC (RFC 2104) over the named hash. An empty secret is rejected -
 * SubtleCrypto refuses a zero-length key, and making the check explicit gives
 * a clearer error than the raw DOMException.
 *
 * @param {string} message  - The payload to authenticate.
 * @param {string} secret   - The shared key.
 * @param {string} [algorithm='SHA-256'] - One of HMAC_ALGORITHMS.
 * @returns {Promise<string>} Lowercase hex HMAC.
 * @throws {Error} On empty secret or unsupported algorithm.
 */
export async function hmacHex(message, secret, algorithm = 'SHA-256') {
  if (!HMAC_ALGORITHMS.includes(algorithm)) {
    throw new Error(`Unsupported HMAC algorithm: ${algorithm}`);
  }
  if (secret.length === 0) {
    throw new Error('HMAC secret must not be empty');
  }

  // importKey binds the raw key bytes to the HMAC algorithm + hash choice.
  // extractable=false and usages=['sign'] keep the key non-exportable and
  // scoped to signing only - the tightest capability the API allows.
  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey(
    'raw',
    encoder.encode(secret),
    { name: 'HMAC', hash: algorithm },
    false,
    ['sign'],
  );

  // sign() produces the tag as an ArrayBuffer; hex-encode for output.
  const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(message));
  return toHex(signature);
}

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 →