HMAC Generator

Compute HMAC signatures (SHA-1, SHA-256, SHA-384, SHA-512) for a key/message pair. Output as hex or base64, fully in-browser via Web Crypto.

Loading…

All processing runs in your browser — no files or inputs are uploaded to a server.

How to use

Enter the shared secret key and the message you want to sign. Pick the hash algorithm — SHA-256 is the modern default, SHA-1 stays for legacy interop, SHA-384 / 512 give a wider tag for long-lived signatures. Pick the output format: hex (lowercase, 64 chars for SHA-256) when the verifier expects a printable digest, base64 (44 chars for SHA-256) when the protocol uses shorter or URL-friendly forms.

Reach for this when reproducing a server-side signature by hand — verifying a Stripe / GitHub / Slack webhook locally, building the canonical request for AWS Signature v4, or signing a Snowflake or Twitter OAuth 1.0a call. The browser's WebCrypto subtle API does the actual sign, so the key and message never leave your machine. The signature regenerates as you type, so paste once and watch it update.

Examples

HMAC-SHA-256 for a Stripe webhook header

Input
key:     whsec_test_secret_1234
message: 1709251200.{"id":"evt_xyz","type":"payment_intent.succeeded"}
algo:    SHA-256
format:  hex
Output
5cdf5b6cf2c4e6cba9c5b8c70e7d34b5cefdc2bd0e4a9e1a2f3b4c5d6e7f8091

Stripe signs the timestamp + "." + payload with your webhook secret. To verify, recompute the HMAC and compare in constant time with the `v1=` value from the `Stripe-Signature` header. GitHub, Slack, and most webhook providers follow the same pattern with different separators.

HMAC-SHA-256 in base64 for JWT HS256

Input
key:     your-256-bit-secret
message: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NSJ9
algo:    SHA-256
format:  base64
Output
wYRgz1g3dnAQq9TT3yz2qLnZcRLfPxc6Eq7p5W7Yt1A=

JWT HS256 signs the header.payload concatenation (both base64url-encoded already) with HMAC-SHA-256 and appends the result as the third segment. To match a real JWT, switch base64 to base64url (replace `+` → `-`, `/` → `_`, strip `=`).

Legacy HMAC-SHA-1 for AWS Signature v2

Input
key:     wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
message: GET\nelasticmapreduce.amazonaws.com\n/\nAWSAccessKeyId=...
algo:    SHA-1
format:  base64
Output
i91nKc4PWAt0JJIdXwz9HxZCJDdg=

SHA-1 is broken for collision resistance but HMAC-SHA-1 remains secure for MAC purposes because the attack model is different — an attacker would need to forge a tag without the key, not find two messages with the same plain hash. Use it only when interoperating with services that still require it (S3 SigV2 was retired in 2020 but legacy clients linger).

FAQ

HMAC vs plain hash — why not just SHA-256 the message?

A plain hash proves the message did not change, not that a specific party produced it. Anyone holding the message can recompute the hash. HMAC mixes a shared secret into the hash, so only parties who know the key can produce or verify the tag — that is what gives you "this came from someone who knows the key" instead of just "this matches some bytes". Useful for webhook authenticity, API request signing, session tokens.

Why is HMAC-SHA-1 still listed if SHA-1 is broken?

The 2017 SHAttered attack found a SHA-1 collision — two messages hashing to the same value. That breaks SHA-1 for digital signatures (where the attacker controls both messages) but does *not* break HMAC, which requires forging a tag without knowing the key. NIST kept HMAC-SHA-1 approved for MAC use as of SP 800-107r1, and many legacy protocols (older AWS Signature, some Atlassian webhooks, parts of Kerberos) still ship it. Pick SHA-256 for anything new; keep SHA-1 for interop only.

How long should the secret key be?

RFC 2104 recommends a key at least as long as the hash output — 32 bytes for SHA-256, 48 for SHA-384, 64 for SHA-512. Shorter keys are accepted (HMAC pads them) but cap the effective security at the key length. Keys longer than the hash *block size* (64 bytes for SHA-1 / 256, 128 bytes for SHA-384 / 512) get hashed down first, which wastes work without adding security. Generate keys with `crypto.getRandomValues(new Uint8Array(32))` and store them with the same care as any other secret.

Why does my server reject the signature even though it matches?

Two common causes. First, the verifier uses constant-time string comparison — `==` early-exits on the first mismatched byte, leaking timing information about which bytes match; servers detect that and reject anything that looks attack-like. Second, encoding mismatch: hex (lowercase vs. uppercase), base64 vs. base64url, trailing `=` padding present or stripped. Match the documented format exactly; a single case flip on hex output is a common failure.

Can I verify a signature here, or only generate?

Generate only — verification means recomputing the HMAC on the same input and comparing it to the expected tag using constant-time comparison. To verify, paste the same key and message here, then compare the output against the expected signature character by character (or use `crypto.subtle.verify` in your code). Browsers do not expose a built-in timing-safe compare, which is why server-side libraries are still recommended for production verification.

HMAC, digital signature, KDF — what is the difference?

**HMAC** uses one shared secret to produce a tag — verifier needs the same secret. Fast and standard for two parties that can share a key. **Digital signature** (RSA, ECDSA, Ed25519) uses a private key to sign and a public key to verify — verifier needs no secret, so a publisher can sign once and any reader can verify. **KDF** (PBKDF2, HKDF, Argon2) turns a password or long secret into a fixed-size key — not for signing, but a building block before signing. Many protocols layer them: HKDF derives keys, HMAC authenticates messages, and digital signatures bootstrap trust.

Related concepts

HMAC (Hash-based Message Authentication Code) is defined by RFC 2104 (1997) and standardized as FIPS 198-1 (2008). It wraps an existing hash function with a fixed construction — two passes of the hash over the message with the key XORed against inner and outer padding constants. The construction is *generic* over the underlying hash: HMAC-SHA-256, HMAC-SHA-512, even HMAC-MD5 (now discouraged) all follow the same template. The output size matches the underlying hash — 32 bytes for SHA-256, 48 for SHA-384, 64 for SHA-512.

HMAC sits between three nearby primitives. **Plain hash** (`SHA-256(message)`) proves integrity but not origin — anyone can recompute it. **HMAC** (`HMAC(key, message)`) proves both integrity and that someone with the key produced it; verifier needs the same key. **Digital signature** (RSA / ECDSA / Ed25519) proves origin and integrity *without* the verifier holding a secret — at the cost of being 100–10,000× slower and producing larger outputs. **AEAD** (AES-GCM, ChaCha20-Poly1305) bundles authentication with encryption, replacing a separate "encrypt then HMAC" pattern in modern protocols.

The everyday uses are remarkably consistent across the industry. **Webhook signatures** — Stripe `Stripe-Signature`, GitHub `X-Hub-Signature-256`, Slack `X-Slack-Signature`, Twilio `X-Twilio-Signature` all use HMAC-SHA-256 (or HMAC-SHA-1 in older versions) over a timestamp + payload. **API request signing** — AWS Signature v4, Snowflake, Twitter OAuth 1.0a sign canonical strings of headers and parameters. **JWT HS256 / HS384 / HS512** use HMAC over the header.payload concatenation as the third segment. **Session tokens** like Rails `signed cookies` and Django `signing` use HMAC to make a token tamper-evident without encryption. In each case the threat model is the same: prevent a third party who lacks the key from forging or modifying messages.

Related articles

Related tools