Skip to content

Encryption Specification

PassBox uses a layered encryption architecture with three cryptographic primitives:

PrimitiveAlgorithmLibraryPurpose
KDFArgon2id@noble/hashesPassword → Master Key
SymmetricAES-256-GCM@noble/ciphersEncrypt/decrypt data
AsymmetricX25519@noble/curvesKey exchange (vault sharing)

All libraries are from the @noble family, audited by Cure53 with zero dependencies.

Derives a 32-byte master key from the user’s password.

ParameterValue
AlgorithmArgon2id
Salt32 random bytes (per user)
Iterations (t)3
Memory (m)65536 KB (64 MB)
Parallelism (p)4
Output length32 bytes
masterKey = argon2id(password, salt, {t: 3, m: 65536, p: 4, dkLen: 32})

The salt and KDF parameters are stored on the server (not secret — they’re inputs to the KDF, not outputs).

All symmetric encryption uses AES-256-GCM with a random IV.

ParameterValue
AlgorithmAES-256-GCM
Key length256 bits (32 bytes)
IV length96 bits (12 bytes, random)
Tag length128 bits (16 bytes)
interface EncryptedBlob {
iv: string; // base64-encoded 12-byte IV
ciphertext: string; // base64-encoded ciphertext
tag: string; // base64-encoded 16-byte auth tag
algorithm: 'aes-256-gcm';
}

This format is used for:

  • Secret values (encrypted with vault key)
  • Private keys (encrypted with master key)
  • Vault keys (encrypted with master key or shared secret)
  • Recovery master key (encrypted with recovery key)

Used to share vault keys between team members.

ParameterValue
AlgorithmX25519 (Curve25519 ECDH)
Private key32 bytes (random)
Public key32 bytes (derived from private key)
Shared secret32 bytes (ECDH output, used as AES key)
Inviter:
sharedSecret = X25519(inviterPrivateKey, targetPublicKey)
encryptedVaultKey = AES-256-GCM(sharedSecret, vaultKey)
→ store encryptedVaultKey for target user
Target user:
sharedSecret = X25519(targetPrivateKey, inviterPublicKey)
vaultKey = AES-256-GCM-decrypt(sharedSecret, encryptedVaultKey)

Both parties derive the same shared secret from their own private key and the other’s public key (ECDH property).

User Password
└─ Argon2id ─→ Master Key (32 bytes)
├─ encrypts: User Private Key (X25519)
├─ encrypts: Vault Keys (one per vault)
└─ encrypted by: Recovery Key
Recovery Key (random, shown once)
└─ encrypts: Master Key backup
Vault Key (random 32 bytes, per vault)
└─ encrypts: Secret Values (AES-256-GCM)
X25519 Key Pair
├─ Public Key (stored plaintext on server)
└─ Private Key (encrypted with Master Key)
  • Generated during registration
  • 24-word format (base64-encoded random bytes)
  • Encrypts the master key with AES-256-GCM
  • Stored on the server (encrypted master key blob)
  • Used to restore master key if password is forgotten
DataReason
Secret namesUsed for indexing and lookup
Vault namesUsed for indexing and lookup
Tags and descriptionsMetadata for organization
TimestampsAudit trail
Email addressesAuthentication
KDF salt and paramsRequired to derive master key
Public keysRequired for key exchange
ThreatMitigation
Server database breachOnly ciphertext stored; AES-256-GCM is computationally secure
Server admin is maliciousZero-knowledge: server never has master key or plaintext
Man-in-the-middleHTTPS/TLS for transport; E2E encryption for data
Password brute forceArgon2id (memory-hard, 64MB per attempt); Supabase bcrypt for auth
Stolen deviceMaster key in auth.json with 0600 permissions; user should logout
Team member removedVault key re-encryption recommended (manual process)