Osy#betaa language · its runtime Osyrin · a hosted platform
Why Osy#Built for agentsAgents as declarationsWorkflows that waitRuns exactly onceSecure by defaultNothing to mockThe editor is the compilerUI in the languageDocuments are dataOne program

Reference / Function

Signing with raw bytes — Crypto.HmacSha256, Sha256, ToHex, and Text.ToBytes

Crypto.HmacSha256(key: byte[], message: byte[]) → byte[] · Crypto.Sha256(byte[]) → byte[] · Crypto.ToHex(byte[]) → string · Crypto.FromHex(string) → byte[] · Text.ToBytes(string) → byte[] · Text.FromBytes(byte[]) → string

The byte-in, byte-out half of the crypto surface. Real request-signing schemes chain their HMACs — each step's raw output becomes the next step's key — and a hex-returning HMAC cannot do that, because the hex text of a digest is not the digest. These take and answer bytes, so the chain composes; hex appears once, at the end.

stable2 examples compiled by CIfunctioncryptohmacsecurity

Summary#

Crypto.HmacSha256Hex answers hex, which is right when the tag is the final answer — verifying a webhook, signing a cookie. It is the wrong shape for a signing chain, and that is what this page is for.

Crypto.HmacSha256(key, message) takes bytes and answers bytes, so its own output is a legal key for the next call. Text.ToBytes gets you bytes from text, and Crypto.ToHex renders the final result.

Signature#

byte[] Crypto.HmacSha256(byte[] key, byte[] message)   // keyed, raw in and raw out — this is the chaining one
byte[] Crypto.Sha256(byte[] data)                      // unkeyed digest, raw
string Crypto.ToHex(byte[] bytes)                      // lowercase hex
byte[] Crypto.FromHex(string hex)                      // back again; either case in

byte[] Text.ToBytes(string s)                          // UTF-8
string Text.FromBytes(byte[] bytes)                    // UTF-8

Description#

Why hex cannot chain#

A scheme like AWS Signature Version 4 derives its signing key in four steps, and each step's raw output is the next step's key:

kDate     = HMAC("AWS4" + secret, date)
kRegion   = HMAC(kDate,   region)
kService  = HMAC(kRegion, service)
kSigning  = HMAC(kService, "aws4_request")
signature = hex(HMAC(kSigning, stringToSign))

Feed the hex text of kDate forward and every later step is keyed on the wrong 64 bytes. Nothing local objects — the code reads correctly, each call succeeds, and the only symptom is that the far end answers 403. That is why these exist as a separate, byte-typed surface rather than as another string overload: the type is what stops the mistake.

Text and bytes are different things#

Text.ToBytes encodes as UTF-8; Text.FromBytes decodes the same way. Everything here is strict about which one it takes — passing a string where a byte[] is wanted is a compile error naming both, rather than a silent encode.

Text.FromBytes is for bytes you know are text. Bytes that are not — an image, a digest — have no meaningful text form; use [[function-crypto-bytes#signature|Crypto.ToHex]] or Convert.ToBase64String to render those.

Examples#

The SigV4 derivation, written line-for-line from the spec above:

string SigV4Signature(string secret, string dateStamp, string region, string service, string stringToSign) {
  var kSecret  = Text.ToBytes("AWS4" + secret);
  var kDate    = Crypto.HmacSha256(kSecret,  Text.ToBytes(dateStamp));
  var kRegion  = Crypto.HmacSha256(kDate,    Text.ToBytes(region));
  var kService = Crypto.HmacSha256(kRegion,  Text.ToBytes(service));
  var kSigning = Crypto.HmacSha256(kService, Text.ToBytes("aws4_request"));
  return Crypto.ToHex(Crypto.HmacSha256(kSigning, Text.ToBytes(stringToSign)));
}

A signed request also carries a hash of its payload, which is usually not text:

string PayloadHash(byte[] body) {
  return Crypto.ToHex(Crypto.Sha256(body));
}

See also#

Related

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…

Crypto.Sha256Hex

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

Http.*

Make an outbound HTTP call to a URL you build at runtime — a webhook, a third-party API, a discovered endpoint…