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#
Crypto.Encrypt(<string> plaintext) -> string // an opaque ciphertext envelope
Crypto.Decrypt(<string> ciphertext) -> string // the original plaintextDescription#
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#
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#
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 — 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#
Store a value encrypted, and search by a hash of it:
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#
- Crypto.Sha256Hex — the deterministic hash to search by (encryption is not searchable)
- Crypto.HmacSha256Hex and Crypto.FixedTimeEquals — authenticate a message from outside your app