Architecture
Encryption Overview
Section titled “Encryption Overview”PassBox implements end-to-end encryption using three cryptographic primitives from the audited @noble library family (Cure53 audited):
| Layer | Algorithm | Purpose |
|---|---|---|
| Key Derivation | Argon2id | Derive master key from password |
| Symmetric Encryption | AES-256-GCM | Encrypt/decrypt secrets and keys |
| Key Exchange | X25519 | Share vault keys between team members |
Key Hierarchy
Section titled “Key Hierarchy”Password (user input) │ ├─ Argon2id(password, salt, params) ──→ Master Key (32 bytes) │ │ │ ├─ AES-256-GCM(masterKey) ──→ encrypts Private Key │ ├─ AES-256-GCM(masterKey) ──→ encrypts Vault Keys │ └─ Recovery Key ──→ AES-256-GCM backup of Master Key │ └─ X25519 Key Pair ├─ Public Key (stored on server) └─ Private Key (encrypted, stored on server)
Vault Key (random 32 bytes, per vault) │ └─ AES-256-GCM(vaultKey) ──→ encrypts each Secret ValueRegistration Flow
Section titled “Registration Flow”- User provides email + password
- Client generates a random salt (32 bytes)
- Client derives master key via Argon2id:
- Iterations: 3
- Memory: 65536 KB (64 MB)
- Parallelism: 4
- Output: 32 bytes
- Client generates X25519 key pair
- Client encrypts private key with master key (AES-256-GCM)
- Client creates recovery key (random 24-word key that encrypts the master key)
- Client sends to server: email, password hash, public key, encrypted private key, encrypted recovery master key, salt, KDF params
- Server creates user account and stores encrypted key material
Login Flow
Section titled “Login Flow”- User provides email + password
- Server authenticates and returns: JWT tokens + encrypted key material (public key, encrypted private key, salt, KDF params)
- Client re-derives master key from password + salt using same KDF params
- Master key is stored locally for the session (base64 in
~/.passbox/auth.json, mode 0600)
Secret Encryption
Section titled “Secret Encryption”When you store a secret:
- Client fetches the vault’s encrypted vault key
- Client decrypts vault key using master key
- Client encrypts the secret value with the vault key:
- Generate random 12-byte IV
- AES-256-GCM encrypt(vaultKey, iv, plaintext) → ciphertext + auth tag
- Client sends encrypted blob to server:
{iv, ciphertext, tag, algorithm: "aes-256-gcm"}
When you retrieve a secret:
- Client fetches encrypted blob from server
- Client decrypts vault key (cached after first use)
- Client decrypts: AES-256-GCM decrypt(vaultKey, iv, ciphertext, tag) → plaintext
Vault Sharing (X25519)
Section titled “Vault Sharing (X25519)”When inviting a team member to a vault:
- Inviter fetches their own encrypted private key + the vault’s encrypted vault key
- Inviter decrypts their private key with their master key
- Inviter decrypts the vault key with their master key
- Inviter fetches the target user’s public key from the server
- Inviter performs X25519 key exchange:
sharedSecret = X25519(myPrivateKey, theirPublicKey) - Inviter encrypts vault key with the shared secret:
AES-256-GCM(sharedSecret, vaultKey) - Server stores the encrypted vault key for the new member
- New member can later decrypt using their own private key + inviter’s public key
What the Server Stores
Section titled “What the Server Stores”| Data | Encrypted? | Can server read? |
|---|---|---|
| No | Yes | |
| Password hash | Bcrypt (by Supabase Auth) | No (one-way) |
| Public key | No | Yes |
| Private key | AES-256-GCM (master key) | No |
| Master key (recovery) | AES-256-GCM (recovery key) | No |
| KDF salt + params | No | Yes (needed for key derivation) |
| Vault keys | AES-256-GCM (master key) | No |
| Secret values | AES-256-GCM (vault key) | No |
| Secret names | No | Yes |
| Vault names | No | Yes |
Libraries Used
Section titled “Libraries Used”@noble/hashes— Argon2id, HKDF@noble/ciphers— AES-256-GCM@noble/curves— X25519
All three libraries are audited by Cure53 and have zero dependencies.