UUID Studio

HMAC-SHA384

Keyed SHA-384 authentication codes.

  • 🔒 No data stored or uploaded
  • âš¡ 100% client-side
  • 🆓 Free, no account

Need more than one tool at a time? Open the full Workbench - or press Ctrl+K to jump to any tool.

Hash, HMAC, AES-GCM/CBC + RSA-OAEP, codecs, JWT decoding, UUID v4, and secure random - all client-side. JWTs use Base64URL (three segments), not a single MIME Base64 block - use Decode JWT below, not raw Base64 decode.

About HMAC-SHA384

HMAC-SHA-384 proves two things at once: that a message has not been altered, and that whoever produced the code held the shared secret. It takes a key and a message and returns a 384-bit code (96 hex characters) that nobody can produce or verify without the key.

The construction is deliberately not a hash of the key and message concatenated. HMAC hashes the message twice using two different key-derived pads, defined in RFC 2104, and that is what makes it immune to the length-extension attack which breaks the naive hash(secret + message) approach. If you have ever written that shortcut, HMAC is the thing to replace it with.

HMAC-SHA384 is uncommon, which makes encountering it informative: it usually means a specification chose it deliberately. You will see it as HS384 in JWTs where an issuer wanted a longer tag, and in TLS cipher suites paired with AES-256. Because SHA-384 uses 64-bit words internally, it is frequently faster than HMAC-SHA256 on 64-bit servers despite the longer output.

Any byte string works as a key. There is no length requirement, but a key shorter than the digest gives away security for nothing, so generate 48 random bytes and store them as a secret rather than typing a passphrase.

Both the key and the message stay in your browser on this page. The code is computed with the Web Crypto API and never transmitted, which is the only safe way to paste a real signing secret into a web tool.

How to use the HMAC-SHA384

  1. Paste the message exactly as the signing side sees it, byte for byte, including any newlines - this is where most mismatches come from.
  2. Paste the shared secret into the key field.
  3. The HMAC-SHA-384 code is computed in your browser and shown as 96 hex characters.
  4. Compare it against the signature you received. In your own code, compare with a constant-time function, never with ==.

Examples

  • Message
    payload

HMAC-SHA384 in code

The same operation this tool performs, in the languages you are most likely to need it.

JavaScript (browser / Web Crypto)
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
  "raw",
  enc.encode("my-shared-secret"),
  { name: "HMAC", hash: "SHA-384" },
  false,
  ["sign"]
);
const sig = await crypto.subtle.sign("HMAC", key, enc.encode("message"));
const hex = [...new Uint8Array(sig)]
  .map((b) => b.toString(16).padStart(2, "0"))
  .join("");
console.log(hex);
Node.js (verifying a webhook)
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, receivedHex, secret) {
  const expected = createHmac("sha384", secret).update(rawBody).digest();
  const received = Buffer.from(receivedHex, "hex");
  // Lengths must match first - timingSafeEqual throws if they differ.
  if (received.length !== expected.length) return false;
  return timingSafeEqual(expected, received);
}

// rawBody MUST be the unparsed body. Re-serialising req.body with
// JSON.stringify changes key order and spacing, and verification fails.
Python
import hmac, hashlib

def sign(secret: bytes, message: bytes) -> str:
    return hmac.new(secret, message, hashlib.sha384).hexdigest()

def verify(secret: bytes, message: bytes, received: str) -> bool:
    expected = sign(secret, message)
    # compare_digest is constant-time; == is not
    return hmac.compare_digest(expected, received)
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

Mac mac = Mac.getInstance("HmacSHA384");
mac.init(new SecretKeySpec(
    "my-shared-secret".getBytes(StandardCharsets.UTF_8), "HmacSHA384"));
byte[] sig = mac.doFinal("message".getBytes(StandardCharsets.UTF_8));

StringBuilder sb = new StringBuilder();
for (byte b : sig) sb.append(String.format("%02x", b));
System.out.println(sb);
Go
package main

import (
	"crypto/hmac"
	"crypto/sha512"
	"encoding/hex"
	"fmt"
)

func main() {
	mac := hmac.New(sha512.New384, []byte("my-shared-secret"))
	mac.Write([]byte("message"))
	fmt.Println(hex.EncodeToString(mac.Sum(nil)))
	// Verify with hmac.Equal, which is constant-time.
}
C#
using System.Security.Cryptography;
using System.Text;

using var hmac = new HMACSHA384(Encoding.UTF8.GetBytes("my-shared-secret"));
byte[] sig = hmac.ComputeHash(Encoding.UTF8.GetBytes("message"));
Console.WriteLine(Convert.ToHexString(sig).ToLowerInvariant());
Command line
printf '%s' "message" \
  | openssl dgst -sha384 -hmac "my-shared-secret" -hex

# printf '%s' rather than echo: echo appends a newline,
# which changes the signature.

When you need this

  • Verifying an HS384 JWT where the issuer chose a longer signature.
  • Matching a MAC from a specification that mandates SHA-384.
  • Signing on 64-bit hardware where SHA-384 outperforms SHA-256 on larger messages.
  • Producing a 384-bit tag for a protocol that requires one.
  • Confirming a 96-character hex MAC is SHA-384 rather than something truncated.

Common problems and what causes them

Assuming the longer digest is slower
HMAC-SHA384 is usually faster than HMAC-SHA256 on 64-bit CPUs for anything but very short messages, because SHA-384 is SHA-512 internally and processes 1024-bit blocks with 64-bit words. Digest length and speed are unrelated here.
Confusing a 96-character hex MAC with a truncated SHA-512
SHA-384 is not the first 384 bits of SHA-512 - it uses different initial constants. A SHA-512 HMAC truncated to 96 hex characters is a different value and will not verify against a real HMAC-SHA384.
Signature mismatch on a webhook the provider says is valid
You are almost certainly signing a re-serialised body. Frameworks parse JSON and re-encode it, changing key order, whitespace and unicode escaping. Capture the raw request body before any body-parser middleware runs, and sign those exact bytes.
Hex vs Base64 signature encoding
The same 384-bit code prints as 96 hex characters or as Base64. GitHub sends hex with a "sha256=" prefix; several other providers send Base64. Decode both sides to bytes before comparing.
Key treated as text when the provider means bytes
If a secret is given to you as hex or Base64, decode it to bytes before using it as the key. Passing the printable string as UTF-8 produces a different key and therefore a different code.
Comparing signatures with == or ===
A normal string comparison returns as soon as it finds a difference, leaking how much of the signature was correct and enabling byte-at-a-time forgery over many attempts. Use timingSafeEqual, hmac.compare_digest, hmac.Equal or MessageDigest.isEqual.
Using a plain hash where a MAC is required
sha256(secret + message) is not a MAC. Because SHA-256 is a Merkle-Damgard construction, an attacker holding one valid digest can append data and compute a valid digest for the longer message without knowing the secret. HMAC exists to prevent exactly that.
A trailing newline in the message
A shell heredoc, a file read, or echo will usually add \n where the signing side did not. If your code is close but wrong, try again without the final newline.

FAQ

Why would a specification choose HMAC-SHA384 over HMAC-SHA256?
Usually for a longer tag with better performance on 64-bit hardware, or to pair with AES-256 in a cipher suite for consistent security levels. Practically, HMAC-SHA256 is already beyond brute force, so the choice is about matching a specification rather than closing a real gap.
Is HMAC-SHA384 immune to length extension like SHA-384 is?
The question does not really apply - HMAC is not vulnerable to length extension in any of its variants, because the construction hashes twice with key-derived pads. SHA-384's truncation matters for the bare hash, not for HMAC.
What is the difference between SHA-384 and HMAC-SHA-384?
SHA-384 is a plain hash - anyone can compute it for any input, so it only tells you whether data changed by accident. HMAC-SHA-384 mixes in a secret key, so only someone holding the key can produce or check the code. That is what makes it proof of origin as well as integrity.
How long should the secret key be?
Any length is accepted, but use at least 48 random bytes so the key matches the digest size. Keys longer than the hash block get hashed down first, so extreme length adds nothing. Generate it randomly - a human-chosen passphrase is guessable.
Why does my HMAC differ from the server's for the same message?
In order of likelihood: the body was re-serialised instead of signed raw; there is a trailing newline; the signature is Base64 on one side and hex on the other; or the key was used as text when it should have been decoded from hex or Base64.
Can an HMAC be reversed to recover the key or the message?
No - it is built on a one-way hash and has no inverse. A weak, guessable key can be brute-forced offline by an attacker holding one message and its code, which is the reason to use random keys.
Is HMAC-SHA-384 still considered secure?
Yes. HMAC's security does not rest on the collision resistance of its hash, which is why even HMAC-SHA1 has no practical break. HMAC-SHA-384 is a current, recommended choice.
Is my secret key safe to paste here?
The computation runs in your browser through the Web Crypto API; the key and message are never transmitted or stored. You can confirm that in your network tab, or load the page once and then use it offline.

Related reading