E2E Encryption
Overview
Ravi uses end-to-end encryption for all sensitive data in the credential vault. Credentials are encrypted client-side before being sent to the server. The server stores only ciphertext and can never read your passwords or secrets.
How it works
Key derivation
When you first log in, you choose a 6-digit PIN. This PIN is used to derive your encryption keys:
PIN + server-stored salt
→ Argon2id (time=3, mem=64MB, threads=1)
→ 32-byte seed
→ X25519 keypair (public + private key)
- The PIN never leaves your machine
- The salt is stored server-side (one per account)
- The derived public key is uploaded to the server
- The derived private key stays local in
~/.ravi/auth.json
Encryption
Data is encrypted using NaCl SealedBox:
plaintext
→ SealedBox seal (ephemeral X25519 + Poly1305 MAC)
→ ciphertext stored as "e2e::<base64>"
SealedBox provides anonymous encryption — anyone can encrypt to your public key, but only the holder of the private key can decrypt.
What’s encrypted
| Data | Encrypted? |
|---|---|
| Passwords (password field) | Yes |
| Passwords (username, notes) | Yes |
| Vault secrets (value field) | Yes |
| Vault secrets (notes) | Yes |
| Email content at rest | Yes |
| Email content in real-time events | No (plaintext via SSE) |
| Secret keys (names) | No (needed for lookup) |
| Password domains | No (needed for lookup) |
Zero-knowledge guarantee
The server never sees:
- Your PIN
- Your private key
- Plaintext passwords or secret values
A server-side breach exposes only ciphertext. Without your PIN, the data is unreadable.
PIN verification
To prevent wrong-PIN errors from silently corrupting data, the server stores a known ciphertext (the encrypted string "ravi-e2e-verify"). On login, the client:
- Derives the keypair from PIN + salt
- Checks that the derived public key matches the server’s stored public key
- Decrypts the verifier to confirm the PIN is correct
You get 3 PIN attempts per login. There is no server-side lockout — the verification is purely cryptographic.
Recovery key
During first-time setup, a recovery key is saved to ~/.ravi/recovery-key.txt. Back this up. If you forget your PIN, the recovery key is the only way to regain access to your encrypted data.
Cross-platform compatibility
The same encryption format is used across all Ravi clients:
- Go CLI —
internal/crypto/package - OpenClaw plugin —
src/crypto.ts(TypeScript) - API — ciphertext format
"e2e::<base64>"
Data encrypted by the CLI can be decrypted by the OpenClaw plugin and vice versa, as long as the same PIN is used.
Transparent operation
As a user, you never need to think about encryption. The CLI and plugins handle it automatically:
# This auto-encrypts before sending to the server
ravi passwords create example.com --password "S3cret!" --json
# This auto-decrypts when reading from the server
ravi passwords get <uuid> --json
# → {"password": "S3cret!"}
Next steps
- Credential Vault — passwords and secrets
- Security Model — full security architecture