HMAC Generator

Generate or verify HMAC signatures (SHA-256, SHA-512, etc.) from a message and a secret key.

Output Format
HMAC Result

HMAC is calculated using the browser's built-in crypto.subtle (Web Crypto). The message and secret key are not sent to the server, and the secret key is not stored in the browser — it will disappear upon refresh.

SHA-1 HMAC is for legacy compatibility. For new designs, use SHA-256 or higher. When verifying signatures on the server, do not compare strings with ==; use timing-safe comparison methods (e.g., hash_equals, crypto.timingSafeEqual).

What is the HMAC Generator?

Generate or verify HMAC (Hash-based Message Authentication Code) signatures with this secure, browser-based tool. HMAC is essential for tasks like signing API requests or validating webhook payloads, as it proves both message integrity and authenticity. All cryptographic operations run directly in your browser using the Web Crypto API; your secret key and message are never sent to any server. This free tool supports SHA-256, SHA-512, and more, with flexible input and output formats (hex, Base64) to match any requirement.

How to use

  1. Select `🔏 Generate` or `✅ Verify` mode at the top.
  2. Enter your source data into the `Message` field and your private key into the `Secret Key` field. Select the key's format: `Text`, `Hex`, or `Base64`.
  3. Choose the `Hash Algorithm` (e.g., SHA-256) and the desired `Output Format` (e.g., hex).
  4. The signature appears instantly in the `HMAC Result` box. Use the `Copy` button to get the value.
  5. In `✅ Verify` mode, paste the signature you received into the `Expected Value` field to see if it matches the calculated result.
  6. Click `Load example` to populate all fields with sample data and see how the tool works.

HMAC Generator guide

How this tool is used in real work, and what to watch out for.

The #1 Mistake When Verifying Webhook Signatures

When building a server to receive webhook notifications, like for a completed payment, you must verify the signature. If you don't, anyone can fake a "payment successful" request and get your product for free. But when you first write the verification code, the signature will almost certainly mismatch. The cause is almost always the same: you're re-serializing a parsed object to generate the signature.

The sender signs the exact body bytes they are transmitting. If the receiver parses the body into a JSON object and then re-serializes it back to a string, subtle differences in whitespace, line breaks, key order, or character escaping will appear. If even one byte is different, the HMAC will be completely different. You must always use the raw body bytes for verification.

javascript
// Express: This route must receive the raw body (place before global express.json())
app.post("/webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.get("X-Signature") || "";
    const mine = crypto.createHmac("sha256", process.env.WEBHOOK_SECRET)
      .update(req.body)          // The raw Buffer. Do not parse and then re-serialize.
      .digest("hex");

    const a = Buffer.from(sig, "utf8");
    const b = Buffer.from(mine, "utf8");
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));  // Parse only after successful verification.
    res.sendStatus(200);
  });
If you have a global `express.json()` middleware, it will consume the raw body, causing signature verification to fail permanently. Make sure your webhook route is defined before the JSON parser so it can receive the raw body. The principle is the same for any framework.

What to Check When Signatures Don't Match

If you select "Verify" in the tab above, an input field for the expected value will appear. Paste the signature sent by the other party, and the tool will compare it with the newly calculated value and show whether it matches or not. It automatically strips GitHub-style `sha256=` prefixes and recognizes both hex and Base64 formats. If they don't match, check the following in order.

  • Secret Key Format — The same key string will produce completely different bytes depending on whether it's interpreted as Text, Hex, or Base64. If the documentation says "a hex-encoded key," you must select the Hex option. This is the most common source of error.
  • Output Format — If the other party expects Base64 and you send hex, it will obviously fail. If the lengths are almost double or half what you expect, this is likely the issue.
  • Algorithm — Is it SHA-256 or SHA-1? You can tell immediately by the number of resulting bytes (32 vs 20).
  • Whitespace or line breaks in the message — A single newline character copied by mistake will change the result. CRLF and LF are different bytes.
  • Encoding — If the message contains non-ASCII characters, both sides must use the same encoding. This tool encodes the message as UTF-8.
  • What is being signed — Is it just the body, or a timestamp and body concatenated in a specific format? This varies by service, so check the integration documentation.
If the result shows "Cannot parse expected value," it means the length is incorrect, not that the value is wrong. A SHA-256 signature must be 64 characters for hex or 44 characters for Base64.

Why You Shouldn't Use `==` for Comparison (Timing Attacks)

String comparisons typically check byte by byte from the beginning and stop immediately if they find a difference. This means that comparing a string that's wrong from the first byte takes less time than comparing one that's correct for the first ten bytes. By changing the signature slightly and measuring the response time thousands of times, an attacker can find the points where the comparison takes fractionally longer, indicating another correct byte has been found, and eventually reconstruct the entire correct signature.

Measuring this difference over the actual internet is difficult due to network noise. However, it becomes statistically viable within the same data center or if the attacker can send a massive number of requests. Most importantly, the defense costs nothing — all you have to do is use a different function.

LanguageFunction to use
Node.jscrypto.timingSafeEqual(a, b) — Throws if lengths differ, so check length first
PHPhash_equals($expected, $actual)
Pythonhmac.compare_digest(a, b)
JavaMessageDigest.isEqual(a, b)
Gohmac.Equal(a, b)
This tool's verification also compares the full byte array without early exit by accumulating XORs. While code running in a browser isn't a target for timing attacks, it's better to maintain good habits.

HMAC vs. Simple Hashing: What's the Difference?

You might think, "Can't I just prepend the secret key and hash it?" But the `sha256(secret + message)` approach is actually vulnerable. Many hash functions, including SHA-256, are susceptible to length extension attacks due to their internal structure. An attacker who knows the hash of `secret+message` and the `message` itself can calculate a correct hash for a longer message with arbitrary data appended, without ever knowing the secret. In effect, they can forge a valid signature for a modified message.

HMAC is immune to this attack because it uses a construction that wraps the data in an inner and outer hash using the key. This is why the standard advice is to never roll your own signing scheme and always use HMAC.

MethodForgery PreventionAssessment
sha256(message)None — anyone can calculateIntegrity only, not authentication
sha256(secret + message)Vulnerable to length extension attacksDo not use
HMAC-SHA256(secret, message)SecureThe standard. This tool's default
The SHA-1 option is provided for compatibility with legacy systems. While HMAC-SHA1 is not considered broken today, there is no good reason to use anything less than SHA-256 for new designs.

What a Valid Signature Can't Prevent: Replay Attacks

HMAC only verifies one thing: that the message came from a party who has the key, and that it was not altered in transit. It says nothing about *when* the message was created or if you've already processed it. An attacker can copy a valid request and send it 100 times, and the signature will pass verification every single time.

  • Include a timestamp in the data to be signed, and reject requests that are more than, say, five minutes old. The timestamp must be part of the signed payload, otherwise an attacker can just change it.
  • Record event IDs to ignore messages you've already processed (idempotency). Webhooks can sometimes be delivered more than once even under normal conditions.
  • Even with a valid signature, always cross-reference critical values like amounts and order numbers with your own database records.
  • The secret key and message are processed only within this browser and are not sent to the server. The secret key is not even stored and will disappear on refresh. Still, pasting production keys into web tools is not recommended — use test keys to confirm the format, then move the logic into your code.

Frequently asked questions

How is HMAC different from a standard hash (like SHA-256)?

A standard hash verifies integrity (if data changed), but anyone can create it. HMAC requires a secret key, proving authenticity—that it came from a trusted source.

Is my secret key safe when using this tool?

Yes, completely. All HMAC calculations run locally in your browser. Your secret key and message are never sent to our server, and the key is cleared on page refresh.

How do I use this to verify a webhook signature?

Go to `✅ Verify` mode. Paste the webhook payload into `Message` and your secret into `Secret Key`. Then, paste the signature from the header into `Expected Value` to check for a match.

What format should the `Expected Value` be in?

Paste the signature as-is. The tool automatically recognizes hex, Base64, and Base64URL formats. Prefixes like `sha256=` (used by GitHub) are also stripped, so no cleanup is needed.