# Crypto.HmacSha256Hex and Crypto.FixedTimeEquals

> HMAC-SHA-256 authenticates a message under a shared key — proving it came from a key holder and was not altered, which a plain hash cannot do. Always verify the resulting tag with Crypto.FixedTimeEquals, never with ==, because ordinary equality leaks how many bytes matched and lets an attacker forge a tag byte by byte.

<!-- id: function-crypto-hmac · area: function · stability: stable · html: https://osysharp.com/reference/function/crypto-hmac/ -->

## Summary        {#summary}
`Crypto.HmacSha256Hex(key, message)` returns the HMAC-SHA-256 tag of `message` under `key` as a **64-character
lowercase hex string**. Unlike a plain hash, it is **keyed**: only someone holding the key can produce a valid tag, so
the tag proves the message came from a key holder and was not altered.

`Crypto.FixedTimeEquals(a, b)` compares two tags **in constant time**. Verify every received tag with it — never with
`==`.

## Signature      {#signature}
```osy syntax
Crypto.HmacSha256Hex(<string> key, <string> message) -> string
Crypto.FixedTimeEquals(<string> a, <string> b) -> bool
```

## Description    {#description}
Use HMAC whenever you must trust a message that arrived from outside: a webhook payload, a signed URL parameter, an
API callback. A plain hash cannot do this job — an attacker who rewrites the payload just recomputes its hash. Because
the HMAC tag depends on a key only you and the sender know, it cannot be recomputed by a third party.

The `key` and the `message` are distinct roles and are **not interchangeable**: swapping them produces a different
(and wrong) tag. The key should come from configuration or the secret store, never a literal in source.

### Verify with FixedTimeEquals, never `==`      {#verify}
This is the part that is easy to get wrong, so it is worth being precise about. String equality **short-circuits**: it
returns as soon as it hits the first differing byte. That means comparing a *wrong* tag that shares a long prefix with
the correct one takes measurably **longer** than one that differs immediately. An attacker who can submit many guesses
and time the responses can exploit that difference to discover the correct tag one byte at a time — and then forge a
valid signature, defeating the whole mechanism.

`Crypto.FixedTimeEquals` compares the full length regardless of where the values differ, so the time it takes reveals
nothing about how close a guess was. A length mismatch simply returns `false` (it does not throw).

The rule is unconditional: **any value being checked against a secret — an HMAC tag, a signature, a token — is
compared with `Crypto.FixedTimeEquals`.**

Both functions run **in memory**. `Crypto.HmacSha256Hex` has no SQL push-down form (a constant-time comparison is
meaningless once a database is doing the matching), so using them in a query predicate is a compile error rather than
a silently weaker check.

## Examples       {#examples}
Verifying a signed webhook — the canonical use, and the canonical mistake it prevents:

```osy title="verify a signed webhook payload" test app=crypto-hmac
// The sender signs the payload with the shared key; we recompute the tag and compare.
// FixedTimeEquals is what makes this safe to expose to an attacker who can retry.
bool IsAuthenticWebhook(string payload, string receivedSignature, string sharedKey) {
  var expected = Crypto.HmacSha256Hex(sharedKey, payload);
  return Crypto.FixedTimeEquals(expected, receivedSignature);
}

// WRONG — never do this. `==` short-circuits on the first differing byte, leaking through
// its timing how much of the tag a guess got right, which lets an attacker forge one:
//   return Crypto.HmacSha256Hex(sharedKey, payload) == receivedSignature;
```

## See also       {#see-also}
- [Signing with raw bytes — Crypto.HmacSha256, Sha256, ToHex, and Text.ToBytes](https://osysharp.com/reference/function/crypto-bytes/) — the byte-in/byte-out HMAC, for a signing CHAIN where each output keys the next call
  (SigV4 and friends). Hex cannot chain: the hex text of a digest is not the digest.
- [Crypto.Sha256Hex](https://osysharp.com/reference/function/crypto-sha256hex/) — the unkeyed secure hash (integrity, not authentication)
- [Crypto.Md5Hex](https://osysharp.com/reference/function/crypto-md5hex/) — the non-adversarial checksum, and why it is not a security primitive
