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

> 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.

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

## Summary        {#summary}
[`Crypto.HmacSha256Hex`](https://osysharp.com/reference/function/crypto-hmac/) 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      {#signature}
```osy syntax
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    {#description}

### Why hex cannot chain      {#why-bytes}
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**:

```text
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-and-bytes}
`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       {#examples}
The SigV4 derivation, written line-for-line from the spec above:

```osy title="AWS SigV4's signing key and signature" test app=crypto-bytes
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:

```osy title="hash the request payload" test app=crypto-bytes
string PayloadHash(byte[] body) {
  return Crypto.ToHex(Crypto.Sha256(body));
}
```

## See also       {#see-also}
- [Crypto.HmacSha256Hex and Crypto.FixedTimeEquals](https://osysharp.com/reference/function/crypto-hmac/) — the hex-returning HMAC, for when the tag IS the answer, and `FixedTimeEquals`
- [Crypto.Sha256Hex](https://osysharp.com/reference/function/crypto-sha256hex/) — the unkeyed hash over text
- [Http.*](https://osysharp.com/reference/http/facade/) — `Http.Put(url, bytes, contentType)`, which is what a signed request is usually attached to
