Skip to main content

Cryptography

The algorithms Tale uses for data at rest, data in transit, password hashing, and audit-log integrity — and how they map to BSI TR-02102-1.

5 min read

This page is the inventory of every cryptographic primitive Tale relies on: what protects secrets on disk, what protects traffic on the wire, how passwords are hashed, and how the audit log proves it has not been tampered with. It is written for operators and compliance reviewers who need to answer "which algorithms, which key lengths, where are the keys" against a standard such as BSI TR-02102-1 — Tale already uses compliant primitives, and this page is where they are written down.

The claims here are verified against the source; where a primitive is configurable, the environment variable that controls it is named so you can audit your own deployment. None of this is a substitute for encrypting the host disk — see Hardening for the layer below the application.

Data at rest

Tale encrypts two classes of secret at rest, with two different mechanisms.

Provider API keys live in providers/*.secrets.json and are encrypted with SOPS using an age key. SOPS encrypts each value with AES-256-GCM and wraps the data key to the age recipient, whose key agreement is X25519. An encrypted value reads ENC[AES256_GCM,data:…,iv:…,tag:…] on disk; decryption happens in-process and the age private key never leaves the platform container's memory.

Application-encrypted fields — OAuth integration tokens and similar credentials stored in the database — are encrypted with AES-256-GCM through a compact JWE (alg: dir, enc: A256GCM). The 32-byte key comes from ENCRYPTION_SECRET (base64) or ENCRYPTION_SECRET_HEX (hex); the platform refuses to start the encryption path with a key that is not exactly 32 bytes.

The Convex data store and Postgres volumes are protected by the host: run them on an encrypted filesystem (LUKS, or your cloud provider's volume encryption). Tale does not store credentials in plaintext — a provider key or OAuth token is either SOPS-encrypted on disk or AES-256-GCM-encrypted in the database, never written in the clear.

Customer PII and application records — names, email and postal addresses, conversation content — are protected at rest by the same layers that protect the database as a whole: Convex's at-rest encryption, TLS 1.3 in transit, and row-level security that scopes every read to the caller's organisation.

Application-level field encryption is purpose-built for secrets — provider keys and OAuth tokens, written once and read by a single code path. PII is different: it is filtered, sorted, and looked up by exact value, and the customer table is indexed by organisation and email. Encrypting those columns at the field level would break equality lookups and indexed search — unless paired with a searchable-hash scheme that leaks the very equality it is meant to hide — while adding a key-rotation cost and no protection the encrypted host disk beneath the application doesn't already provide against a stolen volume.

If your compliance regime calls for field-level PII encryption on top of these layers, that is a deliberate application change rather than a default Tale ships.

Data in transit

All browser and API traffic terminates TLS at the reverse proxy (Caddy), which negotiates TLS 1.3 (with TLS 1.2 as the floor) and obtains certificates automatically. The cipher suites are the proxy's modern defaults — AES-256-GCM and ChaCha20-Poly1305 with ECDHE key exchange. Configure the domain and certificate source in TLS and domains; traffic between containers stays on the host's internal Docker network.

Password hashing

Local-password accounts are hashed with bcrypt (via Better Auth), so a stolen database row does not reveal the password and a verification deliberately costs ~100 ms — which is also why the login path's timing is fuzzed (see Authentication). Sessions are signed with BETTER_AUTH_SECRET (HMAC); rotating that secret invalidates every existing session.

Audit-log integrity

The audit log is tamper-evident through a SHA-256 hash chain: each entry stores SHA-256(previousHash + canonicalized record), so altering or deleting any historical entry breaks the chain at that point and every entry after it. Entries additionally carry an HMAC-SHA-256 signature. The admin integrity-check verifies both; see Audit logs.

Mapping to BSI TR-02102-1

Every primitive below is in the recommended set of BSI TR-02102-1. Tale does not ship any deprecated algorithm (no MD5, SHA-1, DES, or RSA < 3072 on a key it generates).

UseAlgorithmKey / output sizeControlled by
Provider secrets (disk)AES-256-GCM + age (X25519)256-bitSOPS_AGE_KEY / SOPS_AGE_KEY_FILE
App fields (database)AES-256-GCM (JWE dir/A256GCM)256-bitENCRYPTION_SECRET / ENCRYPTION_SECRET_HEX
TransportTLS 1.3 (AES-256-GCM, ECDHE)256-bitReverse proxy / tls-and-domains
Password hashingbcryptper-hash saltBetter Auth (built in)
Session signingHMAC-SHA-256256-bitBETTER_AUTH_SECRET
Audit integritySHA-256 chain + HMAC-SHA-256256-bitbuilt in

Key storage and rotation

Three secrets are load-bearing, and each has a rotation path. The age private key (SOPS_AGE_KEY) decrypts provider secrets; rotate it by adding a new recipient and re-encrypting, following the walk in Secrets with SOPS. The field-encryption key (ENCRYPTION_SECRET) decrypts database credentials; rotating it requires re-encrypting the affected rows, so plan it as a maintenance step rather than a hot swap. The auth secret (BETTER_AUTH_SECRET) signs sessions; rotating it logs everyone out on their next request. All three live only in the platform container's environment — never commit them, and store them in your secret manager of record.

Where this fits

Cryptography in Tale is layered: SOPS+age and AES-256-GCM protect secrets at rest, TLS 1.3 protects them in transit, bcrypt protects passwords, and a SHA-256 chain proves the audit log is intact — all primitives that sit inside BSI TR-02102-1's recommended set, with the controlling environment variables named above so you can verify your own instance.

The layer beneath the application is the host itself: Hardening covers the egress allowlist, container isolation, and disk-encryption expectations that this page assumes, and Secrets with SOPS is the operational walk-through for the age key these algorithms depend on.

© 2026 Tale by Ruler GmbH — ISO 27001 & SOC 2 certified.

Tale is MIT licensed — free to use, modify, and distribute.