Encryption
Kachd provides end-to-end encryption (E2EE) for direct messages, voice calls, and video chat. All cryptography uses the browser's built-in Web Crypto API with no external dependencies. The Kachd server never has access to your plaintext messages or private keys.
DM Encryption: Key Setup
When you first use encrypted DMs, a setup dialog guides you through generating your encryption keys:
- An X25519 ECDH key pair is generated entirely in your browser
- A 12-word recovery phrase is created using the BIP39 wordlist (128-bit entropy with a 4-bit SHA-256 checksum)
- You must write down the recovery phrase and confirm it before proceeding
- Your private key is encrypted using PBKDF2 (100,000 iterations, SHA-256) with the recovery phrase as the passphrase, then wrapped in AES-256-GCM
- The encrypted private key, salt, nonce, and your public key (JWK format) are uploaded to the server
- Your raw private key is stored locally in IndexedDB for fast access
DM Encryption: How Messages Work
One-on-One DMs
Your private key and the recipient's public key are combined using X25519 ECDH key agreement to derive a 256-bit shared secret. This shared secret is used as an AES-GCM key. Each message is encrypted with AES-256-GCM using a random 12-byte nonce. Only the ciphertext and nonce (both base64-encoded) are sent to the server — plaintext never leaves your device.
Group DMs
A random per-message AES-256-GCM key is generated for each message. The message is encrypted with this key. The per-message key is then wrapped (encrypted) individually for each group member using the shared secret derived from ECDH with that member's public key. Each recipient unwraps the message key with their own shared secret, then decrypts the message.
Performance
Derived shared secrets are cached in memory (up to 50 entries) so the ECDH key agreement doesn't need to be repeated for every message. Public keys from other users are also cached (up to 100 entries) to minimize server requests.
DM Encryption: Key Recovery
If you log in on a new device, you can recover your keys using your 12-word recovery phrase. The phrase decrypts your encrypted private key that is stored on the server.
- Lost your recovery phrase? Encrypted messages cannot be recovered. You can generate new keys, but messages encrypted with the old keys will become permanently unreadable.
- You can verify key authenticity with contacts by comparing key fingerprints (SHA-256, displayed as 16 hex pairs like ab:cd:ef:...)
Voice & Video Encryption
All voice and video channels use end-to-end encryption by default — there is no way to disable it. When you join a voice or video channel:
- The server generates a per-session E2EE passphrase by computing HMAC-SHA256 over a channel identifier combined with a random 16-byte session nonce
- This passphrase is sent to your client and loaded into LiveKit's ExternalE2EEKeyProvider
- A dedicated Web Worker handles the encryption pipeline using Insertable Streams (WebRTC Encoded Transform), encrypting and decrypting media frames off the main thread
- The LiveKit server and Kachd only see encrypted media frames — they cannot access the audio or video content
- A fresh key and worker are created for each voice session. The worker is terminated when you disconnect to prevent memory leaks.
What Is Not Encrypted
For transparency, the following are not end-to-end encrypted:
- Community channel messages — Text, file, and forum channel messages are stored in plaintext in the database
- File uploads in community channels — Files stored on Cloudflare R2 are not encrypted at the application layer (R2 provides encryption-at-rest)
- Message metadata — Timestamps, sender IDs, and channel IDs are visible to the server regardless of E2EE status