Click the Castor logo or press Ctrl Alt T to change theme.
# Symmetric encryption for application data Symmetric encryption is the right tool when the same application that encrypts a value must decrypt it later. In web apps, that usually means protecting data at rest: OAuth refresh tokens, API keys, private notes, webhook secrets, or integration credentials. Castor Crypto uses XChaCha20-Poly1305 through `Aead`. It is an authenticated encryption primitive: decryption only succeeds if the ciphertext, key, nonce, and additional data all match. ## Encrypt a database field ```php <?php use Castor\Crypto\Aead; use Castor\Crypto\Base64; use Castor\Crypto\Secret; use Castor\Crypto\SecretText; $key = Aead::fromBase64($_ENV['FIELD_ENCRYPTION_KEY'], Base64::UrlSafeNoPadding); $ciphertext = $key->encrypt( Secret::raw($refreshToken), aad: 'users:'.$userId.':refresh_token', ); $databaseValue = $ciphertext->toBase64(Base64::UrlSafeNoPadding); ``` To read it back: ```php $ciphertext = SecretText::fromBase64($databaseValue, Base64::UrlSafeNoPadding); $refreshToken = $key->decrypt( $ciphertext, aad: 'users:'.$userId.':refresh_token', )->toString(); ``` `encrypt()` stores the nonce inside the returned ciphertext as `nonce || encrypted message`. You do not need a nonce column and you should not invent your own nonce scheme for the normal case. ## Why additional data matters Additional authenticated data, often called AAD, is not encrypted. Instead, it is checked during decryption. This lets you bind ciphertext to context your application already knows. A good AAD value answers: "where is this ciphertext allowed to be used?" ```php $aad = 'tenants:'.$tenantId.':users:'.$userId.':slack_token'; ``` If an attacker can edit your database, they might copy Alice's encrypted token into Bob's row. Without AAD, that swap may decrypt cleanly. With row-specific AAD, decryption fails because Bob's row provides different context. > [!IMPORTANT] > AAD must be reproduced exactly during decryption. Use stable identifiers, not display names or values that may legitimately change. ## Key storage and rotation The encryption key protects every value encrypted with it. Do not store it next to the ciphertext in the same table. Prefer a secret manager, deployment secret, or KMS-backed environment injection. A simple rotation strategy is to version keys: ```php $payload = [ 'kid' => 'field-key-2026-01', 'ciphertext' => $ciphertext->toBase64(Base64::UrlSafeNoPadding), ]; ``` The key id is not secret. It tells your application which key to try. During rotation, decrypt with the old key and re-encrypt with the new key when the record is written again. ## Tradeoffs and attack vectors Encryption protects confidentiality after a database leak, but it does not protect data from application code that legitimately decrypts it. If an attacker gets remote code execution in your app, they can usually access both ciphertext and key. Authenticated encryption detects tampering. Treat `CryptoError` during decryption as a failed authentication check and return a generic application error. Do not partially process decrypted data after a failure. Backups matter too. If old backups contain old keys, deleted ciphertext may still be recoverable. Align key rotation and backup retention with your threat model.
Castor ecosystem