HMAC Generator

Generate an HMAC from a message and a secret key using SHA-256 or SHA-512. The usual reason to be here is a webhook that keeps failing verification: compute the expected signature and compare it with what your code produces. The Web Crypto API signs in this tab, so the key is never sent anywhere.

Message
Secret Key
Generated HMACs

1 Supported algorithms

HMAC-SHA256

Start with HMAC-SHA256. Stripe and GitHub webhooks, AWS Signature V4 and the HS256 algorithm in JWT all use it.

HMAC-SHA512

The same construction over SHA-512. Choose it when the other side specifies it; it adds no practical safety over SHA-256.

2 HMAC Generator example output

Generated HMAC

1
2
HMAC-SHA256:
d93591bdf7a9d2d6a362547a0701c6010d293125211915995537905132z8787f

3 What people use it for

Debugging webhook signatures

Stripe, GitHub and most payment providers sign requests with HMAC-SHA256. Paste the raw request body and your signing secret here: if the result matches the header, your code is wrong; if it does not, check the secret.

Checking an implementation

Run the same message and key through this page and through your code, then compare the digests. A mismatch points at encoding, whitespace or the key, before you start debugging the network.

How to use the HMAC Generator tool

  1. Paste the message to sign into the Message editor, or click Upload File to load it from a text file.
  2. Type the shared secret into the Secret Key field. The key stays in your browser.
  3. The HMAC-SHA1, HMAC-SHA256, HMAC-SHA384 and HMAC-SHA512 results update as you type, shown as lowercase hex.
  4. Hover a result and click its copy button to copy that signature.
  5. Dummy Data fills in a sample message and key. Clear resets both fields.

Signing a message with HMAC-SHA256 in JavaScript, PHP and Python

JavaScript / Node.js

const { createHmac, timingSafeEqual } = require("node:crypto");

const secret = "my-secret-key";
const message = "hello world";

const hex = createHmac("sha256", secret).update(message).digest("hex");
console.log(hex);
// 90eb182d8396f16d4341d582047f45c0a97d73388c5377d9ced478a2212295ad

// Compare signatures in constant time, never with ===
function verify(received, expected) {
  const a = Buffer.from(received, "hex");
  const b = Buffer.from(expected, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
console.log(verify(hex, hex)); // true

// Browser: Web Crypto
async function hmacSha256(key, text) {
  const enc = new TextEncoder();
  const k = await crypto.subtle.importKey("raw", enc.encode(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
  const sig = await crypto.subtle.sign("HMAC", k, enc.encode(text));
  return [...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, "0")).join("");
}
hmacSha256(secret, message).then(console.log);

PHP

<?php
$secret  = 'my-secret-key';
$message = 'hello world';

$hex = hash_hmac('sha256', $message, $secret);
echo $hex, PHP_EOL;

// Base64 output (what many webhook providers send)
echo base64_encode(hash_hmac('sha256', $message, $secret, true)), PHP_EOL;

// Verify a signature from a header in constant time
$received = $hex;
var_dump(hash_equals($hex, $received)); // bool(true)

For webhooks, sign the raw request body from php://input, not the decoded array, or the bytes will not match.

Python

import base64
import hashlib
import hmac

secret = b"my-secret-key"
message = b"hello world"

digest = hmac.new(secret, message, hashlib.sha256)
print(digest.hexdigest())
print(base64.b64encode(digest.digest()).decode())

# Verify a webhook signature in constant time
received = digest.hexdigest()
print(hmac.compare_digest(received, digest.hexdigest()))  # True

# From the shell:
#   printf 'hello world' | openssl dgst -sha256 -hmac 'my-secret-key'

Do not name your script hmac.py; it would shadow the standard library module and fail with a circular import error.

Common errors and how to fix them

Signature mismatch: my HMAC is hex but the provider sends Base64 (or the reverse)
Both encode the same 32 bytes. Compare in one format: decode the provider's Base64 to bytes and compare with your raw digest, or encode yours the same way they do.
Webhook verification fails even though the secret is right
You signed a re-serialized body. JSON.parse then JSON.stringify changes whitespace and key order. Sign the exact raw bytes the provider sent, and include any timestamp prefix their scheme requires (Stripe uses t=timestamp.body).
The HMAC changes when I paste the same key
A trailing space or newline in the key or message changes every bit of the output. Trim both, and check whether the provider expects the key as hex or Base64 bytes rather than as text.
Comparing with == or === passes tests but is flagged in review
Plain comparison exits at the first differing byte, which leaks timing information. Use timingSafeEqual, hash_equals or hmac.compare_digest.

Good to know

HMAC mixes a secret key into a hash so that only someone holding the key can produce or check the tag. A bare hash cannot do that: with plain SHA-256, anyone who sees the message can compute the digest, and length-extension attacks let them extend it. HMAC closes both holes (RFC 2104 has the construction). The tag is the same size as the underlying hash, so 32 bytes for HMAC-SHA256, shown as 64 hex characters.

HMAC-SHA256 is the common choice: webhooks from GitHub, Stripe and Shopify, AWS Signature v4, JWT HS256. HMAC-SHA1 is still sound as a MAC, because HMAC does not rely on collision resistance. However, pick SHA-256 for anything new, because security scanners and auditors flag SHA-1 regardless of how it is used. Use a random key at least as long as the hash output.

Specifications and references

This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.

Guides and tutorials

Frequently Asked Questions

What is the difference between a hash and an HMAC?

A hash depends only on the message. An HMAC also depends on a secret key, so only someone who knows the key can produce or verify it. This is why webhooks and API request signing use HMAC.

Which algorithms are supported?

HMAC with SHA-1, SHA-256, SHA-384 or SHA-512. HMAC-SHA256 is the most common choice.

Where is HMAC used in practice?

Webhook signatures (Stripe, GitHub, Shopify, Slack), API request signing (AWS Signature v4), JWTs with HS256, and cookie or session integrity checks.

Can I verify a webhook signature with this tool?

Yes. Paste the raw request body as the message and your signing secret as the key, then compare the output with the signature header the provider sent.

Is my secret key sent anywhere?

No. The Web Crypto API computes the HMAC in your browser tab, and the page sends nothing.

Related

For a digest without a key, use the SHA generator. HMAC-SHA256 also signs most JWTs, so the JWT decoder and JWT generator cover the token side.

More Security & Hashing Tools

Browse all security & hashing tools

Copied to clipboard!