Your files never leave your device. All processing happens locally in your browser.
How do I generate or verify an HMAC without my secret key leaving the browser?
Open the HMAC Generator & Verifier, type or paste a MESSAGE and a SECRET KEY, choose a hash (SHA-1, SHA-256, SHA-384 or SHA-512), and the browser’s native Web Crypto API computes the HMAC live via <code>crypto.subtle.sign(‘HMAC’, …)</code> exactly as defined by RFC 2104. Each field has its own UTF-8 / hex / base64 encoding selector, so a raw binary key can be entered byte-for-byte, and the digest is shown in hex (upper or lower case), base64 and base64url with copy buttons and its byte length. Switch to Verify mode to paste an expected HMAC (hex or base64): it is decoded to bytes and compared to the freshly computed digest with a constant-time-style XOR equality that avoids an early exit on the first differing byte. Everything runs on your device — the key, message and expected value are never uploaded, and the random-key button uses crypto.getRandomValues, never Math.random.
Message + secret key, each with a UTF-8 / hex / base64 encoding selector
SHA-1 / SHA-256 / SHA-384 / SHA-512 via the native Web Crypto API (RFC 2104)
Output in hex (upper/lower), base64 and base64url with the digest byte length
Verify mode decodes the expected HMAC to bytes and compares in constant-time style
100% client-side — nothing uploaded; random key from crypto.getRandomValues
What is
HMAC (Hash-based Message Authentication Code)
An HMAC is a message authentication code built from a cryptographic hash function and a shared secret key, standardised in RFC 2104 as HMAC(K, m) = H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ m)). It lets two parties who share the key confirm that a message is unchanged (integrity) and was produced by someone holding the key (authenticity). Because verification requires the same secret, an HMAC is symmetric — it is not a digital signature (it provides no non-repudiation) and not encryption (it does not conceal the message). This tool computes it 100% in the browser with the Web Crypto API.
No. The HMAC is computed entirely in your browser with the Web Crypto API, and nothing you type is uploaded.
The tool is 100% client-side. Your message, secret key and any expected HMAC are just strings in memory that are passed to the browser’s native <code>window.crypto.subtle</code> — there is no network request, no upload, no logging and no CDN in the processing path. The random-key button draws from <code>crypto.getRandomValues</code>, never <code>Math.random</code>. The tool works offline once cached, keeps no history, and the only optional network use anywhere on the site is consent-gated ads, which never see your inputs. The moment you paste a real secret into an online tool that round-trips to a server you can no longer be sure it stayed private; here, by construction, it did.
An HMAC uses one shared secret, so anyone with the key can create or verify it; a digital signature uses a private/public key pair and proves who signed.
HMAC is symmetric: the same secret key both produces and verifies the code, so it proves integrity and authenticity only among parties who already share that secret. Anyone holding the key can forge a valid HMAC, which means it gives no non-repudiation — you cannot prove to a third party WHICH holder created it. A digital signature (RSA, ECDSA, Ed25519) is asymmetric: the signer uses a private key and anyone can verify with the public key, so it does prove authorship. HMAC is faster and simpler and is the right tool for API request signing, webhooks and JWT HS256; use a real signature when you need non-repudiation or public verification.
HMAC-SHA1 has no known practical break and is still accepted, but prefer SHA-256 or above for new work.
The SHA-1 collision attacks that broke certificates do not translate into a practical forgery against HMAC, because HMAC’s security relies on the hash behaving like a pseudo-random function rather than on collision resistance. That is why HMAC-SHA1 is still permitted in many protocols and libraries. Even so, SHA-1 is deprecated generally, so for anything new you should choose SHA-256 (the recommended default) or SHA-384 / SHA-512 — this tool offers all four and shows the digest length update to 20, 32, 48 or 64 bytes so you can match whatever a given API expects.
It decodes both values to bytes and compares them with an XOR-accumulate loop that has no early exit; it is best-effort, not guaranteed constant-time.
Comparing two HMACs with a normal string equality can leak information: a check that stops at the first differing character takes measurably longer the more leading bytes match, which a remote attacker can exploit to recover a valid tag byte by byte (a timing side-channel). To avoid that, the verifier decodes the expected value and the computed digest to raw bytes and XOR-accumulates every byte before testing the result, so the work does not depend on WHERE the first difference is. Be honest about the limit, though: a JIT-compiled language such as JavaScript cannot guarantee true constant time, and the up-front length check leaks the length. Treat this as useful defence in depth for a client-side aid, not a substitute for a hardened, constant-time comparison on a server.
Detailed Explanation
📖How It Works
What the HMAC Generator & Verifier Does
The HMAC Generator & Verifier is a 100% client-side tool that computes a Hash-based Message Authentication Code (RFC 2104) from a message and a shared secret key, and can also verify a supplied HMAC. You enter a message and a key — each with its own UTF-8 / hex / base64 encoding selector so a raw binary key can be given byte-for-byte — pick a hash (SHA-1, SHA-256, SHA-384 or SHA-512), and the browser’s native Web Crypto API produces the digest live via crypto.subtle.sign. The result is shown in hex (upper or lower case), base64 and base64url with copy buttons and its byte length. A verify mode decodes a pasted expected HMAC and compares it to the freshly computed digest over raw bytes using a constant-time-style equality. Nothing you type is uploaded; the key, message and expected value never leave the browser tab.
Computes HMAC from a message + shared secret key, per RFC 2104
SHA-1 / SHA-256 / SHA-384 / SHA-512 via the native Web Crypto API
Message and key each support UTF-8 text, hex or base64 input
Output in hex (upper/lower), base64 and base64url with the digest byte length
Verify mode compares a pasted HMAC over decoded bytes, constant-time style
⚙️Methodology
How It Computes the HMAC (Real Web Crypto, RFC 2104)
The tool does not re-implement any cryptography and ships no crypto library. It parses each input to raw bytes with a pure engine (hmacEngine.ts) — UTF-8 via TextEncoder, hex (tolerating 0x, whitespace and colons), or base64/base64url (tolerating whitespace and missing padding) — then imports the key bytes with crypto.subtle.importKey({ name: ‘HMAC’, hash }) and calls crypto.subtle.sign(‘HMAC’, key, messageBytes). That native call performs the RFC 2104 construction HMAC(K, m) = H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ m)) with the browser’s vetted implementation. The returned ArrayBuffer is rendered to hex, base64 and base64url in the engine. Computation is re-run on every keystroke through a cancellable async effect, and the random-key button fills the key with 32 bytes from crypto.getRandomValues (never Math.random). The digest length tracks the hash: 20 bytes for SHA-1, 32 for SHA-256, 48 for SHA-384, 64 for SHA-512.
Uses crypto.subtle.importKey + crypto.subtle.sign — no re-implemented or bundled crypto
HMAC is the workhorse of symmetric message authentication. It signs API requests (AWS Signature v4 derives keys and HMACs the canonical request), secures webhooks so a receiver can confirm a payload really came from the sender (GitHub uses HMAC-SHA256 prefixed with “sha256=”, Stripe HMACs “{timestamp}.{body}”, Slack “v0:{ts}:{body}”, Shopify a base64 HMAC of the raw body), and it is the “HS256/HS384/HS512” family that signs JSON Web Tokens. Developers use a tool like this to reproduce a provider’s expected signature and debug the classic “signature mismatch” ticket — usually a wrong key encoding, the wrong bytes being signed, or a trailing newline. Because the key encoding matters (many secrets are base64 or hex, not text), the per-field encoding selector here lets you match the provider exactly, and the verify mode lets you paste their signature and confirm a match before you ship code.
API request signing (e.g. AWS SigV4) and webhook verification
GitHub, Stripe, Slack, Shopify all sign payloads with HMAC
Signs JWTs in the HS256 / HS384 / HS512 algorithms
Debugs “signature mismatch”: wrong key encoding or wrong signed bytes
An HMAC only means something if the secret key stays secret: anyone holding the key can forge a valid code, so HMAC gives integrity and authenticity but NOT non-repudiation — it is not a digital signature (RSA/ECDSA/Ed25519) and cannot prove which key-holder produced a message. It is also not encryption: the message is authenticated, not hidden. HMAC-SHA1 has no known practical forgery (HMAC security does not depend on collision resistance), so it remains acceptable, but SHA-256+ is recommended for new work. The verify mode’s comparison XOR-accumulates the decoded bytes to avoid an early exit on the first mismatch, but a JIT-compiled language such as JavaScript cannot guarantee true constant time and the length check leaks the length — so it is best-effort defence in depth, not a hardened server-side check. Finally, HMAC by itself does not stop replay attacks; real protocols also bind a timestamp or nonce into the signed bytes and reject stale requests.
Security collapses if the key leaks — anyone with the key can forge an HMAC
No non-repudiation: it is not a digital signature and does not identify the signer
Not encryption — it authenticates the message, it does not conceal it
Constant-time compare is best-effort in JS; the length check leaks length
HMAC alone does not prevent replay — bind a timestamp/nonce in real protocols
🔒Privacy & Security
Private by Design
Everything runs on your device. The message, secret key and any expected HMAC are passed to the browser’s native Web Crypto API (window.crypto.subtle) in local JavaScript — there is no network request, no upload, no logging, and no CDN in the processing path. The random-key generator draws from crypto.getRandomValues, the correct cryptographic source, never Math.random. Nothing is persisted: the tool keeps no history, and closing the tab discards every value. It works offline once cached, supports dark mode and a mobile-first responsive layout, and carries no analytics or telemetry. The only optional network use anywhere on the site is consent-gated ads, which never see your inputs. This matters because pasting a live signing secret into a server-backed “online HMAC” tool means trusting that server with the very key whose secrecy the whole scheme depends on; here, by construction, the key never leaves your browser.
HMAC computed locally via window.crypto.subtle — nothing uploaded
Random key uses crypto.getRandomValues, never Math.random
No history, no logging; values are gone when you close the tab
Works offline once cached; no analytics or telemetry
Only optional network use is consent-gated ads, which never see your inputs
HMAC vs other ways to authenticate or protect a message
Mechanism
Keys
What it proves
Used here
HMAC (this tool)
One shared secret key
Integrity + authenticity among key-holders; fast; no non-repudiation
Integrity only — anyone can recompute it, so it authenticates nothing
No (use a hash tool)
Encryption (AES-GCM)
Shared or public key
Confidentiality (+ integrity for AEAD); hides the message
No (use an encryption tool)
HMAC authenticates but does not encrypt, and gives no non-repudiation because the key is shared. Everything here runs in the browser via the Web Crypto API; keys and messages are never uploaded. SHA-1 HMAC is still accepted but prefer SHA-256+. As of July 2026.