Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

Asymmetric signing — Crypto.SignRs256, VerifyRs256, SignEs256, VerifyEs256

Crypto.SignRs256(privateKeyPem: string, data: byte[]) → byte[] · Crypto.VerifyRs256(publicKeyPem: string, data: byte[], signature: byte[]) → bool · Crypto.SignEs256(privateKeyPem: string, data: byte[]) → byte[] · Crypto.VerifyEs256(publicKeyPem: string, data: byte[], signature: byte[]) → bool

Sign with a private key, verify with a public one. This is what a signed JWT is made of, and a signed JWT is how almost every service authenticates a machine rather than a person — Snowflake key-pair auth, a Google service account, a GitHub App. It is also the inbound direction: checking that a webhook really came from who it says.

stable2 examples compiled by CIfunctioncryptosigningjwt

Summary#

Crypto.HmacSha256Hex proves a message came from someone who holds the same key you do. That works when you and one partner share a secret. It does not work when you need to prove who you are to a service you have never shared a secret with — and that is what every machine-to-machine login asks for.

These sign with a private key and verify with the matching public one. The service holds only the public half, so nothing you send it can ever be replayed as your identity.

RS256 and ES256 are the two names you will meet. They are the same idea with different maths: RS256 is RSA and is what Snowflake, Google and GitHub Apps ask for; ES256 is elliptic-curve, is what Apple asks for, and has much smaller keys.

Signature#

Crypto.SignRs256(privateKeyPem: string, data: byte[]) → byte[]
Crypto.VerifyRs256(publicKeyPem: string, data: byte[], signature: byte[]) → bool

Crypto.SignEs256(privateKeyPem: string, data: byte[]) → byte[]
Crypto.VerifyEs256(publicKeyPem: string, data: byte[], signature: byte[]) → bool

Description#

The key comes from a secret, never from your source. Declare it, then set it from the file the provider gave you:

app.Secrets = [ new Secret("SnowflakeKey") ];
osy secret set SnowflakeKey --file rsa_key.p8

--file is not a convenience. A PEM is several lines, and a value typed on the command line reaches your shell history; the file is how the key arrived and is the right thing to point at.

Both PEM flavours are accepted — -----BEGIN PRIVATE KEY----- (PKCS#8) and -----BEGIN RSA PRIVATE KEY----- (PKCS#1) — because a key is pasted from whatever the issuer handed over, and being told it is the wrong flavour of the same key is a refusal with nothing in it. A key that will not parse throws, and the message never quotes the key.

Verify answers false; it does not throw. A signature that does not check out is the ordinary outcome of examining something you did not write, not an error. Only a malformed key throws, because that one is your own misconfiguration and reporting it as "the sender is forging" would send you to investigate the wrong end.

These run on the server, always. There is no browser implementation and there will not be one: a private key the browser can observe is a private key that has leaked. See The standard library — and where each call runs for the whole execution-side table.

Examples#

A signed JWT, which is the shape nearly every service wants. The three parts are base64url text joined by dots, and the signature covers the first two:

app.Secrets = [ new Secret("SnowflakeKey"), new Secret("PartnerPublicKey") ];

string B64Url(byte[] b) {
  return Convert.ToBase64String(b).Replace("+", "-").Replace("/", "_").Replace("=", "");
}

string MintJwt(string issuer, string subject, long expiresAt) {
  var header  = B64Url(Text.ToBytes("{\"alg\":\"RS256\",\"typ\":\"JWT\"}"));
  var payload = B64Url(Text.ToBytes("{\"iss\":\"" + issuer + "\",\"sub\":\"" + subject + "\",\"exp\":" + expiresAt + "}"));
  var signingInput = header + "." + payload;
  var signature = Crypto.SignRs256(Secret.SnowflakeKey, Text.ToBytes(signingInput));
  return signingInput + "." + B64Url(signature);
}

The inbound direction — a webhook signed with a key whose public half the sender published:

[AllowAnonymous]
bool WebhookIsGenuine(string body, string signatureBase64) {
  return Crypto.VerifyRs256(
    Secret.PartnerPublicKey,
    Text.ToBytes(body),
    Convert.FromBase64String(signatureBase64));
}

See also#

Related

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…

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…

reading a secret's value (Secret.Name)

`Secret.Name` in a function body evaluates to the declared secret's value — the key itself, as a string, read at the…