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-8Description#
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#
- Crypto.HmacSha256Hex and Crypto.FixedTimeEquals — the hex-returning HMAC, for when the tag IS the answer, and
FixedTimeEquals - Crypto.Sha256Hex — the unkeyed hash over text
- Http.* —
Http.Put(url, bytes, contentType), which is what a signed request is usually attached to