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

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

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

## Summary        {#summary}
[`Crypto.HmacSha256Hex`](https://osysharp.com/reference/function/crypto-hmac/) 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      {#signature}
```osy syntax
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    {#description}
**The key comes from a secret, never from your source.** Declare it, then set it from the file the provider gave you:

```osy syntax
app.Secrets = [ new Secret("SnowflakeKey") ];
```
```console
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](https://osysharp.com/reference/stdlib/index/) for the whole execution-side table.

## Examples       {#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:

```osy title="mint an RS256 JWT" test app=crypto-sign
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:

```osy title="verify a signed webhook" test app=crypto-sign
[AllowAnonymous]
bool WebhookIsGenuine(string body, string signatureBase64) {
  return Crypto.VerifyRs256(
    Secret.PartnerPublicKey,
    Text.ToBytes(body),
    Convert.FromBase64String(signatureBase64));
}
```

## See also       {#see-also}
- [Crypto.HmacSha256Hex and Crypto.FixedTimeEquals](https://osysharp.com/reference/function/crypto-hmac/) — the shared-key alternative, for when you and one partner hold the same secret
- [Signing with raw bytes — Crypto.HmacSha256, Sha256, ToHex, and Text.ToBytes](https://osysharp.com/reference/function/crypto-bytes/) — `Text.ToBytes`, `Convert.ToBase64String` and the rest of the byte plumbing these use
- [reading a secret's value (Secret.Name)](https://osysharp.com/reference/function/secret-read/) — reading a declared secret in a body
- [Giving a secret its value](https://osysharp.com/reference/local/giving-a-secret-its-value/) — `osy secret set`, including `--file` for a PEM
