End-to-End Encryption
Private vaults use client-side end-to-end encryption. The server never sees plaintext file contents, raw encryption keys, or your passphrase.
Key hierarchy
Section titled “Key hierarchy”Tusky uses a 3-layer key hierarchy:
Passphrase (user-provided) └─ PBKDF2 (600,000 iterations, SHA-256) └─ Wrapping Key (256-bit) ├─ Verifier (HMAC-SHA-256) — stored on server └─ Decrypts → Master Key (256-bit, random) └─ Wraps → Per-file Key (256-bit, random) └─ Encrypts → File Content (AES-256-GCM)| Key | Purpose | Where it lives |
|---|---|---|
| Master Key | Wraps/unwraps per-file keys | Server (encrypted with wrapping key) |
| Wrapping Key | Encrypts master key, verifies passphrase | Derived at runtime, never stored |
| Recovery Key | Backup wrap of master key | User’s responsibility (shown once) |
| Per-file Key | Encrypts one file | Server (wrapped with master key) |
Cryptographic parameters
Section titled “Cryptographic parameters”| Parameter | Value |
|---|---|
| Symmetric cipher | AES-256-GCM |
| Key derivation | PBKDF2, SHA-256, 600,000 iterations |
| Salt | 128-bit (16 bytes) |
| AES key / IV / tag | 256-bit / 96-bit / 128-bit |
Before using private vaults, set up encryption with a passphrase:
// Using the CLI// $ tusky encryption setup
// Using the SDK — the web app handles this via UI// Setup sends: salt, verifier, encryptedMasterKey, masterKeyWrappedBackuptusky encryption setup# Enter passphrase (min 8 chars)# Save your recovery key — it's shown onceThe setup process:
- Generates a random 256-bit master key
- Derives a wrapping key from your passphrase via PBKDF2
- Computes a verifier (HMAC-SHA-256) for passphrase validation
- Encrypts the master key with the wrapping key
- Generates a recovery key and wraps the master key with it as backup
- Sends encrypted data to the server — the server never sees the raw keys
Uploading encrypted files
Section titled “Uploading encrypted files”For private vaults, the client encrypts before uploading:
- Generate a random 256-bit per-file key and 96-bit IV
- Encrypt the file with AES-256-GCM using the per-file key
- Wrap the per-file key with the master key (AES-256-GCM)
- Compute SHA-256 checksum of the plaintext
- Upload the ciphertext via the standard upload flow
- Include encryption metadata:
wrappedKey,encryptionIv,plaintextSizeBytes,plaintextChecksumSha256
The SDK and CLI handle this automatically when uploading to a private vault with an active encryption session.
Downloading encrypted files
Section titled “Downloading encrypted files”- Get the download URL and encryption metadata from the API
- Download the encrypted bytes
- Unwrap the per-file key using the master key
- Decrypt the file with AES-256-GCM
- Verify the SHA-256 checksum matches
const { downloadUrl, encryption } = await tusky.files.getDownloadUrl(file.id);const response = await fetch(downloadUrl);const encrypted = new Uint8Array(await response.arrayBuffer());
// Decrypt client-side using encryption.wrappedKey and encryption.iv// The CLI and web app handle this automaticallyChanging your passphrase
Section titled “Changing your passphrase”Changing your passphrase re-wraps the master key with a new wrapping key. The master key itself doesn’t change, so all existing encrypted files remain accessible:
tusky encryption change-passphraseA new recovery key is generated — save it securely.
Recovery
Section titled “Recovery”If you forget your passphrase, use the recovery key:
tusky encryption recover# Enter recovery key# Set a new passphraseSecurity properties
Section titled “Security properties”The server never sees:
- Plaintext file contents (private vaults)
- Raw master key, wrapping key, recovery key, or per-file keys
- Your passphrase
Integrity guarantees:
- AES-GCM authenticated encryption detects any tampering
- SHA-256 checksum provides additional integrity verification
- Constant-time comparison prevents timing attacks on verifier checks
Threat model:
- A compromised server only obtains encrypted master keys — useless without the passphrase
- PBKDF2 with 600,000 iterations provides brute-force resistance
- If both server and recovery key are compromised, the master key can be recovered