Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Function

Crypto.HmacSha256Hex and Crypto.FixedTimeEquals

Crypto.HmacSha256Hex(key, message) → string (64-char lowercase hex) · Crypto.FixedTimeEquals(a, b) → bool

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.

stable1 example compiled by CIfunctioncryptohmacsecurity

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#

Crypto.HmacSha256Hex(<string> key, <string> message) -> string
Crypto.FixedTimeEquals(<string> a, <string> b) -> bool

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 ==#

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#

Verifying a signed webhook — the canonical use, and the canonical mistake it prevents:

// 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#

  • Crypto.Sha256Hex — the unkeyed secure hash (integrity, not authentication)
  • Crypto.Md5Hex — the non-adversarial checksum, and why it is not a security primitive

Related

Crypto.Sha256Hex

SHA-256 of a string's UTF-8 bytes as 64-character lowercase hex — the secure default hash. Deterministic, so it pushes…

Crypto.Md5Hex

MD5 of a string's UTF-8 bytes rendered as 32-character lowercase hex — the canonical C# fingerprint form…