# Crypto.Encrypt and Crypto.Decrypt

> Encrypt and decrypt values under your app's key, which the platform mints, protects, and rotates for you. There is no key parameter — you never handle key material. Ciphertext is bound to your app, so it is meaningless to anyone else, and encrypting the same value twice gives different ciphertext, so it cannot be used as a lookup key.

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

## Summary        {#summary}
`Crypto.Encrypt(plaintext)` returns an opaque ciphertext string; `Crypto.Decrypt(ciphertext)` returns the original
value. Both run under **your application's own encryption key**, which the platform generates, protects, and rotates.
You never see, choose, store, or pass a key.

## Signature      {#signature}
```osy syntax
Crypto.Encrypt(<string> plaintext)  -> string   // an opaque ciphertext envelope
Crypto.Decrypt(<string> ciphertext) -> string   // the original plaintext
```

## Description    {#description}
Use this for values that must be readable by your app but must not sit in the database in the clear — a stored
third-party credential, an account number, a piece of sensitive personal data. The encryption is AES-256-GCM, which
both conceals the value **and** authenticates it: a ciphertext that has been altered fails to decrypt rather than
quietly returning corrupted data.

### There is no key parameter — on purpose        {#no-key-parameter}
This is the most important thing about this surface. A `Crypto.Encrypt(key, plaintext)` form does not exist, and will
not be added. The moment an app supplies its own key, that key has to live somewhere — and in practice it ends up
committed in source or stored next to the data it protects, which protects nothing. So the platform owns key
management entirely: it mints a key per application, keeps it encrypted at rest, and can rotate it without your app
changing a line.

Two consequences follow, and both are enforced cryptographically rather than by convention:

- **Your ciphertext is yours.** The application is bound into every ciphertext, so a value encrypted by your app
  cannot be decrypted by another — even if the ciphertext leaks, and even though the platform holds every key.
- **Rotation doesn't strand your data.** Each ciphertext records which key wrote it, so values encrypted before a
  rotation keep decrypting afterwards.

### Ciphertext is not a lookup key        {#not-a-lookup-key}
**Encrypting the same value twice gives you two different ciphertexts.** This is required for the encryption to be
sound — reusing the randomness would let an attacker recover the key — but it has a practical consequence worth
stating plainly:

> You cannot find a row by encrypting a value and matching on the result. That query will never match.

If you need to *look up* by a sensitive value, store a **hash** of it alongside the ciphertext and search on the hash
(see [Crypto.Sha256Hex](https://osysharp.com/reference/function/crypto-sha256hex/) — it is deterministic, so it does work in a query). Encrypt what you need to read
back; hash what you need to search by.

`Crypto.Encrypt` and `Crypto.Decrypt` run in memory and are not available inside a query.

## Examples       {#examples}
Store a value encrypted, and search by a hash of it:

```osy title="encrypt what you read back, hash what you search by" test app=crypto-encrypt
entity PaymentMethod {
  string Ciphertext;    // the value itself — recoverable, never stored in the clear
  string Fingerprint;   // a SHA-256 hash — deterministic, so it IS searchable
}

void StoreCard(string accountNumber) {
  new PaymentMethod {
    Ciphertext = Crypto.Encrypt(accountNumber),
    Fingerprint = Crypto.Sha256Hex(accountNumber),
  };
}

// Decrypting is the only way to see the value again.
string RevealCard(PaymentMethod pm) {
  return Crypto.Decrypt(pm.Ciphertext);
}

// Look up by the HASH, never by the ciphertext — encrypting the same number again would
// produce a different envelope, so a ciphertext match would never find anything.
PaymentMethod FindCard(string accountNumber) {
  return PaymentMethod.FirstOrDefault(p => p.Fingerprint == Crypto.Sha256Hex(accountNumber));
}
```

## See also       {#see-also}
- [Crypto.Sha256Hex](https://osysharp.com/reference/function/crypto-sha256hex/) — the deterministic hash to search by (encryption is not searchable)
- [Crypto.HmacSha256Hex and Crypto.FixedTimeEquals](https://osysharp.com/reference/function/crypto-hmac/) — authenticate a message from outside your app
