Crypto math · · updated
HMAC generation via Web Crypto: local keys and message integrity
HMAC is a keyed hash. In the browser, crypto.subtle.importKey('raw', keyBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']) then subtle.sign. The key stays in a CryptoKey if extractable is false. Pasting a production secret into a web HMAC form is a rotate-now event. Use the HMAC generator and keep unkeyed hashes on SHA-256 checksum.
Construction
HMAC = hash( (K xor opad) || hash((K xor ipad) || message) ). You do not implement that by hand; SubtleCrypto does. SHA-1 HMAC still appears in old APIs; prefer SHA-256 or SHA-512.
importKey and sign
Raw key bytes from TextEncoder or hex parse. Hash SHA-256 or SHA-512. Output ArrayBuffer to hex. This is the body of the HMAC generator.
const key = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
const mac = await crypto.subtle.sign("HMAC", key, data);Timing-safe compare
If verifying locally, do not === on hex if you care about timing. XOR-accumulate lengths that are already equal length. For JWT HS256 the compare is on the signature segment after base64url.
JWT HS256
The signature segment is HMAC over the first two segments. The inspector decodes; this article signs. Never paste the HS256 secret into a third party. Full decode-vs-verify is JWT locally.
Passwords are not HMAC keys
Derive with PBKDF2 or Argon2 if the secret is a password. HMAC-SHA256(password, message) is not a password hash. Unkeyed file integrity is SHA-256 / file hash calculator.
Network proof
A local HMAC of a message should not POST the key. Audit the Network tab. Cloud HMAC sites exist to collect keys.
Worked example: signing a canonical JSON body
APIs often want HMAC-SHA256 of a canonical string (method, path, timestamp, sha256 of body). Build that string in the tab, run the HMAC generator, attach the hex as a header. The key never goes to our origin. If the body is a file, hash the file first, then HMAC the hex — or HMAC the bytes if the spec says so. Do not HMAC a Base64 wrapper unless the spec says that too (Base64 vs Blob).
JWT HS256 is this pattern with a fixed payload format. Debug claims in the JWT inspector. Recompute the signature only with a non-production key, or with a production key that never left your password manager into a website form. Web Crypto is the platform; this article is the keyed mode.
If the secret is a user password, stop. Run PBKDF2 or Argon2 and use the derived key as HMAC key material if a protocol demands it. HMAC(password, msg) is visible in old tutorials and is a defect.
Verify: two hex strings, timing-safe compare, length check first. A mismatch is either canonicalization (JSON key order) or the wrong algorithm. Pretty-print JSON before HMAC only if both sides pretty-print. Canonicalization bugs look like “HMAC is broken” and waste hours.
Frequently asked questions
Hex or Base64 output?
APIs differ. The HMAC generator should offer both. Compare using timing-safe equality if you verify.
Can I HMAC a file?
Yes if it fits in memory or you use a streaming WASM HMAC. SubtleCrypto sign is one-shot. Large files: chunked hashing is digest, not HMAC, unless you add a keyed incremental hasher.
HMAC versus digest?
Digest has no key. Anyone can recompute SHA-256. HMAC requires the key to forge. See Web Crypto vs cloud hashing.