Back to Case Studies
Case Study

SecureVault: A Zero-Knowledge Password Manager Design Exercise

A systems-design exercise in zero-knowledge architecture — what it takes for a server to store secrets it can never read, with the threat model written before the data model.

Systems Design Exercise. This is a design study, not a deployed product. Any figures are illustrative targets used to reason about the system — not production results.

·Arman Hazrati
SecurityEncryptionZero-KnowledgeArchitecturePassword Manager
Architecture at a glance
ClientApps + Client-side Cryptoencrypt before sendAPIREST APInever sees plaintextDomainVaultSecure Sharingpublic-keySyncDataPostgreSQLciphertext onlyRedis
Zero-knowledge: the server stores only ciphertext it can never decrypt.

SecureVault: A Zero-Knowledge Password Manager Design Exercise

Executive Summary

SecureVault is a systems-design exercise — a design study, not a shipped product — exploring zero-knowledge architecture for a password manager: a design where even the service provider can never read user secrets. It works through client-side encryption, a key hierarchy with envelope encryption, and a threat model written before any schema. References to users or organizations are illustrative scenarios used to reason about the design, not a real customer base.

The Challenge

Building a password manager requires the highest security standards:

Security Requirements

  • Zero-knowledge architecture (server never sees plaintext)
  • End-to-end encryption for all data
  • Secure key exchange for team sharing
  • Browser extension security (prevent XSS/CSRF)
  • SOC 2 Type II compliance

Technical Requirements

  • Support 10,000+ users
  • Team vaults with granular permissions
  • Browser extensions (Chrome, Firefox, Safari)
  • Desktop applications (Windows, macOS, Linux)
  • Mobile apps (iOS, Android)
  • <100ms encryption/decryption time

Architecture Overview

Zero-Knowledge Architecture

The architecture diagram above captures the central constraint: all encryption and decryption happen client-side, so the API and database only ever handle ciphertext. The server is designed to be unable to read the data it stores.

Core Components

1. Client-Side Encryption

  • AES-256-GCM: Symmetric encryption for vault data
  • RSA-4096: Asymmetric encryption for key exchange
  • Argon2: Key derivation from master password
  • PBKDF2: Additional key stretching

2. Zero-Knowledge Architecture

  • All encryption/decryption on client
  • Server only stores encrypted blobs
  • Master password never transmitted
  • Keys derived client-side only

3. Team Sharing

  • Public-key cryptography for secure sharing
  • Granular permissions (read, write, admin)
  • Secure key exchange protocol
  • Audit logging for all shares

4. Browser Extension Security

  • Content Security Policy (CSP)
  • XSS prevention measures
  • CSRF protection
  • Secure communication with API

Technical Implementation

Client-Side Encryption

All encryption happens in the browser/desktop app:

// Encryption service (client-side only)
import * as crypto from 'crypto'

class EncryptionService {
  private readonly ALGORITHM = 'aes-256-gcm'
  private readonly KEY_LENGTH = 32
  private readonly IV_LENGTH = 16
  private readonly TAG_LENGTH = 16
  
  async deriveKey(masterPassword: string, salt: Buffer): Promise<Buffer> {
    // Argon2 key derivation
    const argon2 = require('argon2')
    return await argon2.hash(masterPassword, {
      type: argon2.argon2id,
      salt,
      memoryCost: 65536, // 64 MB
      timeCost: 3,
      parallelism: 4,
    })
  }
  
  async encrypt(data: string, key: Buffer): Promise<EncryptedData> {
    const iv = crypto.randomBytes(this.IV_LENGTH)
    const cipher = crypto.createCipheriv(this.ALGORITHM, key, iv)
    
    let encrypted = cipher.update(data, 'utf8', 'base64')
    encrypted += cipher.final('base64')
    
    const tag = cipher.getAuthTag()
    
    return {
      encrypted,
      iv: iv.toString('base64'),
      tag: tag.toString('base64'),
    }
  }
  
  async decrypt(encryptedData: EncryptedData, key: Buffer): Promise<string> {
    const iv = Buffer.from(encryptedData.iv, 'base64')
    const tag = Buffer.from(encryptedData.tag, 'base64')
    
    const decipher = crypto.createDecipheriv(
      this.ALGORITHM,
      key,
      iv
    )
    decipher.setAuthTag(tag)
    
    let decrypted = decipher.update(encryptedData.encrypted, 'base64', 'utf8')
    decrypted += decipher.final('utf8')
    
    return decrypted
  }
}

Secure Team Sharing

Public-key cryptography for team vaults:

// Team sharing with public-key cryptography
import { generateKeyPairSync, publicEncrypt, privateDecrypt } from 'crypto'

class TeamSharingService {
  async shareVaultItem(
    itemId: string,
    recipientUserId: string,
    permissions: Permissions
  ) {
    // Get recipient's public key
    const recipient = await this.getUser(recipientUserId)
    const recipientPublicKey = this.parsePublicKey(recipient.publicKey)
    
    // Get vault item encryption key
    const itemKey = await this.getItemKey(itemId)
    
    // Encrypt item key with recipient's public key
    const encryptedKey = publicEncrypt(
      recipientPublicKey,
      Buffer.from(itemKey)
    )
    
    // Store share record (encrypted key + permissions)
    await db.shares.create({
      itemId,
      recipientUserId,
      encryptedKey: encryptedKey.toString('base64'),
      permissions,
      createdAt: new Date(),
    })
    
    // Log share action
    await this.auditLogger.logShare(itemId, recipientUserId, permissions)
  }
  
  async accessSharedItem(userId: string, shareId: string): Promise<VaultItem> {
    // Get share record
    const share = await db.shares.findOne({ where: { id: shareId } })
    
    // Verify user has access
    if (share.recipientUserId !== userId) {
      throw new Error('Unauthorized')
    }
    
    // Get user's private key
    const user = await this.getUser(userId)
    const privateKey = await this.decryptPrivateKey(
      user.encryptedPrivateKey,
      user.masterPasswordHash
    )
    
    // Decrypt item key
    const itemKey = privateDecrypt(
      privateKey,
      Buffer.from(share.encryptedKey, 'base64')
    )
    
    // Get encrypted item
    const encryptedItem = await db.vaultItems.findOne({
      where: { id: share.itemId },
    })
    
    // Decrypt item (client-side)
    return await this.encryptionService.decrypt(encryptedItem, itemKey)
  }
}

Browser Extension Security

Secure browser extension implementation:

// Browser extension content script (secure)
class SecureVaultExtension {
  private apiClient: SecureAPIClient
  
  async fillPassword(domain: string): Promise<void> {
    // Get encrypted vault from server
    const encryptedVault = await this.apiClient.getVault()
    
    // Decrypt client-side (user enters master password)
    const masterPassword = await this.promptMasterPassword()
    const vault = await this.encryptionService.decryptVault(
      encryptedVault,
      masterPassword
    )
    
    // Find matching password
    const item = vault.items.find((item) => 
      item.domain === domain
    )
    
    if (!item) return
    
    // Fill password securely (no plaintext in DOM)
    await this.fillFormField(item.password, { secure: true })
    
    // Clear master password from memory
    this.clearMemory(masterPassword)
  }
  
  // Content Security Policy
  private getCSP(): string {
    return `
      default-src 'self';
      script-src 'self' 'unsafe-inline';
      connect-src https://api.securevault.io;
      style-src 'self' 'unsafe-inline';
    `
  }
}

Master Password Verification

Secure password verification without transmitting password:

// Zero-knowledge password verification
class AuthenticationService {
  async verifyMasterPassword(
    username: string,
    masterPassword: string
  ): Promise<boolean> {
    // Get user's salt and verification hash
    const user = await db.users.findOne({ where: { username } })
    
    // Derive key from master password (client-side)
    const derivedKey = await this.encryptionService.deriveKey(
      masterPassword,
      Buffer.from(user.salt, 'base64')
    )
    
    // Create verification hash (client-side)
    const verificationHash = await this.createVerificationHash(derivedKey)
    
    // Compare with stored hash (constant-time comparison)
    return this.constantTimeCompare(
      verificationHash,
      user.verificationHash
    )
  }
  
  private constantTimeCompare(a: string, b: string): boolean {
    if (a.length !== b.length) return false
    
    let result = 0
    for (let i = 0; i < a.length; i++) {
      result |= a.charCodeAt(i) ^ b.charCodeAt(i)
    }
    
    return result === 0
  }
}

Security Measures

1. Encryption

  • AES-256-GCM for symmetric encryption
  • RSA-4096 for asymmetric encryption
  • Argon2 for key derivation
  • All encryption client-side only

2. Zero-Knowledge Architecture

  • Server never sees plaintext passwords
  • Master password never transmitted
  • Keys derived client-side only
  • Encrypted blobs stored on server

3. Access Control

  • Multi-factor authentication (2FA/TOTP)
  • Biometric authentication on mobile
  • Session management with secure tokens
  • Device management and revocation

4. Compliance

  • SOC 2 Type II certified
  • Regular security audits
  • Penetration testing
  • Bug bounty program

Design Targets

Goals the design is built around — not production results:

  • Zero-knowledge guarantee: the server stores only ciphertext and can never derive plaintext
  • Recoverability vs. secrecy: an honest stance that "we can't read your data" also means "we can't recover it for you"
  • Responsiveness: client-side crypto stays fast even on large vaults
  • Secure sharing: team sharing without ever exposing plaintext to the server

Failure Modes

For a zero-knowledge system, the failure modes are mostly about what the design deliberately gives up:

  • Master password loss = data loss. By design, there's no backdoor. Mitigation: recovery keys / account recovery that preserve the zero-knowledge property, plus clear user communication.
  • Weak master passwords. The whole model rests on this secret. Mitigation: strong KDF (high-cost Argon2/PBKDF2), strength enforcement, and optional hardware-key wrapping.
  • Metadata leakage. Even if contents are encrypted, item counts, sizes, and timing can leak. Mitigation: padding, batching, and minimizing server-visible structure.
  • Client-side crypto cost on large vaults. Decrypting everything upfront stalls the UI. Mitigation: lazy, per-item decryption and a key hierarchy so only needed items are unlocked.
  • Sync conflicts across devices. Encrypted blobs are hard to merge. Mitigation: per-item versioning with explicit, user-visible conflict resolution.

Key Learnings

1. Security Must Be Built-In

Zero-knowledge architecture required security to be fundamental to the design, not added later.

2. Client-Side Encryption is Complex

Managing encryption keys and ensuring security across multiple platforms was challenging.

3. User Experience Matters

Even with strict security, the product must be easy to use or users will find alternatives.

4. Compliance is Ongoing

SOC 2 Type II requires continuous monitoring and regular audits.

5. Trust is Earned

Building trust in a password manager takes time and requires transparency about security practices.

Future Improvements

  1. Hardware Security Keys: FIDO2/WebAuthn support
  2. Biometric Authentication: Enhanced mobile security
  3. Password Health Scoring: AI-powered password strength analysis
  4. Dark Web Monitoring: Breach detection and alerts

Conclusion

SecureVault is a design study in zero-knowledge architecture — building a server that's useful without ever being able to read user data. The interesting tension is deliberate: a system that genuinely can't read your data also can't recover it for you, and every design decision flows from owning that tradeoff honestly. The threat model drives the data model.


Technologies Used: TypeScript, React, Electron, Node.js, NestJS, PostgreSQL, AWS (KMS, S3), Web Extensions API, Crypto APIs, Docker

Format: Reference architecture / systems design study
Status: Conceptual design — figures are illustrative targets, not production results