Skip to content

Encryption

Introduction

Arvel's encryption services provide a simple, convenient interface for encrypting and decrypting text via AES-256-GCM. All encrypted values are authenticated, so their underlying value cannot be modified or tampered with once encrypted.

The Crypt facade is the entry point. It's also what powers the encrypted:* model casts.

Configuration

Before using the encrypter, set the APP_KEY environment variable. Generate one with:

arvel key:generate

This writes a base64-encoded 32-byte key to your .env as APP_KEY=base64:....

Warning

Encryption requires APP_KEY. Calling the encrypter without it raises MissingAppKeyError. Keep the key secret and out of source control — anyone with it can decrypt your data.

Using the Encrypter

from arvel.facades import Crypt

Encrypting Strings

ciphertext = Crypt.encrypt_string("secret message")
plaintext = Crypt.decrypt_string(ciphertext)

Encrypting Values

encrypt / decrypt handle arbitrary JSON-serializable values — they serialize to JSON, then encrypt:

token = Crypt.encrypt({"user_id": 1, "scopes": ["read", "write"]})
data = Crypt.decrypt(token)   # back to the dict

How It Works

The Encrypter uses AES-256-GCM from the cryptography library. The 32-byte key is derived from your APP_KEY with HKDF-SHA256. Each encryption produces a self-describing payload — a version byte, a random 12-byte IV, and the authenticated ciphertext — base64-encoded for transport. Decryption verifies the version and authentication tag, raising DecryptionError on any tampering or version mismatch.

Note

The application encrypter (Crypt, used by encrypted:* casts) and the column-level EncryptedType decorator use different wire formats. They are not interchangeable: data encrypted with one can't be read by the other. Use Crypt / encrypted:* casts for app-key-backed encryption, and EncryptedType only when you manage the raw key yourself.

Generating & Rotating Keys

arvel key:generate          # write a new APP_KEY to .env
arvel key:generate --show   # print a key without writing it
arvel key:generate --force  # overwrite an existing key

Warning

arvel key:rotate is not implemented — it currently exits with a "not implemented" message. Rotating the application key by hand will make all existing ciphertext (encrypted columns, signed values) unreadable, so plan key changes carefully and re-encrypt data as part of the rotation.