Skip to content
Return to Projects
Case Study

FrostVault

A zero-knowledge Windows password manager with deception built in.

The Problem

Mainstream password managers are cloud-first and ask you to trust a third party with your most sensitive secrets. FrostVault keeps everything local and encrypted — and fights back when someone tries to break in.

My Role

Built solo with AI-assisted development — cryptographic design, application logic, the PySide6 UI, and Windows packaging.

Highlights

  • AES-256-GCM authenticated encryption for all vault data at rest
  • Honey Vault serves believable decoy credentials under a wrong master key or duress
  • Freeze Mode and Ice Crystal Fingerprints provide tamper detection
  • Ships as a single standalone Windows executable — no installer, no cloud

Stack

Python 3.12PySide6AES-256-GCMCryptographyPyInstaller

Constraints

  • Offline-first: vault data must never leave the user's machine.
  • Single-binary distribution — no installer, no runtime to ship.
  • Threat model includes coerced access, not just remote attackers.

System Architecture

Interface
PySide6 Desktop UI
Application
Vault Manager
Honey Vault
Freeze Mode
Cryptography
AES-256-GCM
Key Derivation
Ice Crystal Fingerprints
Storage
Encrypted local vault file

Key Trade-offs

The decisions worth defending — what I chose, what I turned down, and why.

Storage architecture

Chose

Single encrypted local file

Rejected

Cloud-synced vault

Removes the third-party trust dependency entirely; matches the offline-first threat model at the cost of cross-device sync.

Wrong-password behaviour

Chose

Honey Vault returns plausible decoys

Rejected

Hard fail with a clear error

A loud failure tells an attacker they have the wrong key. A believable decoy buys time and frustrates duress scenarios.

Crypto stack

Chose

AES-256-GCM via the `cryptography` library

Rejected

Custom-built primitives

Authenticated encryption out of the box, audited implementation, zero novel crypto risk.

What I'd Do Differently

An honest retrospective — the stuff I'd change with more time, more users, or a second pass.

  1. 1Add an automated integrity self-test that runs on every launch and surfaces tampering visibly to the user.
  2. 2Move key derivation parameters to a versioned header so future Argon2 tuning doesn't break old vaults.
  3. 3Ship a portable Linux build — Windows-only narrows the audience more than I expected.

Technical Deep-Dive

Architecture, specifications, and implementation details.

Security Architecture

FrostVault v1.0.0 — IceLegends


#1. Security Principles

FrostVault is designed around three core security principles:

  1. Zero-knowledge at rest — The master password is never stored. Only its Argon2id hash is persisted. If the database is exfiltrated, no credential can be decrypted without the master password.

  2. Minimum plaintext surface — Decrypted data exists only in Python process memory, only while the vault is unlocked. Every page switch, idle timeout, or explicit lock wipes the derived key.

  3. Defense in depth — Multiple independent layers (Argon2id KDF → AES-256-GCM per entry → SQLite encrypted blobs → Honey Vault decoy → Freeze Mode camouflage) mean that no single compromised layer reveals vault contents.


#2. Attack Surface Analysis

┌─────────────────────────────────────────────────────────┐
│  EXTERNAL ATTACK SURFACE                                │
│                                                         │
│  1. Master password input (login screen)                │
│  2. vault.db file on disk                               │
│  3. Process memory while unlocked                       │
│  4. Clipboard (after copy-password action)              │
│  5. Screen (shoulder surfing / screen capture)          │
└─────────────────────────────────────────────────────────┘

##2.1 Master Password Brute Force

  • Mitigation: Argon2id with memory=65536 KB, iterations=3, parallelism=1
  • Cost per guess on modern hardware: ~200–500 ms
  • 1 billion guesses/day rate → ~50 years for a 10-char random password

##2.2 Database File Theft

  • Mitigation: Each credential field is individually AES-256-GCM encrypted
  • Without the master password → derived key is unrecoverable
  • SQLite file contains only ciphertext + nonces + Argon2 parameters

##2.3 Memory Scraping (RAM dump)

  • Mitigation: Derived key is stored as a Python bytes object; wiped on lock via vault.lock()
  • Residual risk: Python garbage collector timing; swap file may contain stale plaintext

##2.4 Clipboard Hijacking

  • Mitigation: clipboard.py auto-clears after 30 seconds using QTimer
  • Clipboard is set via pyperclip and overwritten with empty string on expiry

##2.5 Shoulder Surfing / Screen Capture

  • Mitigation: Freeze Mode (Ctrl+Shift+F) instantly replaces the window with a fake calculator
  • Window title changes to "Calculator" to defeat process-list enumeration

##2.6 Honey Vault (Intruder Misdirection)

  • After 3 failed master password attempts, attacker is shown a convincing decoy vault
  • Real vault silently remains locked; intrusion is logged to DB with timestamp
  • Attacker cannot distinguish decoy from real vault without the master password

#3. Security Control Matrix

ControlTypeImplementationStandard
Password hashingPreventiveArgon2idOWASP PHC
Key derivationPreventivePBKDF2-HMAC-SHA256 (vault key)NIST SP 800-132
Data encryptionPreventiveAES-256-GCM per fieldFIPS 197
Authenticated encryptionDetectiveGCM authentication tag (128-bit)NIST SP 800-38D
Clipboard auto-clearPreventive30-second QTimer wipe
Idle auto-lockPreventive5-minute inactivity timeoutCIS Benchmark
Decoy vaultDeceptiveHoney Vault with fake credentialsCanary trap
Screen camouflagePreventiveFreeze Mode calculator overlay
Intrusion loggingDetectiveSQLite intrusion_log table
No network accessPreventiveNo socket/http calls in codebase

#4. Cryptographic Trust Chain

Master Password (user input — never stored)
        │
        │  Argon2id
        │  memory=65536 KB · iterations=3 · parallelism=1
        │  salt=32 random bytes (stored in DB: master table)
        ▼
Argon2id Hash (stored in DB for verification)

        │
        │  PBKDF2-HMAC-SHA256
        │  salt=32 random bytes (stored in DB: master table)
        │  iterations=100,000
        ▼
256-bit Derived Key (AES key — held in memory only)

        │
        │  AES-256-GCM
        │  nonce=12 random bytes (prepended to each ciphertext)
        ▼
Ciphertext blobs (stored in DB: entries table)

#5. Key Lifecycle

State: LOCKED
  vault._key = None
  All DB reads return encrypted blobs (unusable)

Event: vault.unlock(master_password)
  1. Fetch Argon2 hash from DB
  2. argon2.verify(hash, master_password) → True/False
  3. If True: derive key via PBKDF2
  4. vault._key = derived_key  ← key enters memory
  State: UNLOCKED

Event: vault.lock()
  1. vault._key = None         ← key wiped from memory
  2. Python GC may not immediately free the bytes object
  State: LOCKED

Event: auto-lock (idle timeout)
  Same as vault.lock()

Event: Freeze Mode activated
  Window hidden; vault remains unlocked in memory
  Key is NOT wiped during Freeze Mode (by design — fast restore)

#6. Honey Vault Design

Failed attempts counter stored in: SQLite honey_entries / intrusion_log
MAX_ATTEMPTS = 3

Attempt 1, 2: Normal "Wrong password" message
Attempt 3:    honey.should_show_honey() → True
              LoginScreen shows "Intruder detected! Loading decoy vault…"
              After 1.2s delay → VaultDashboard.activate(is_honey=True)

Honey mode:
  - Displays pre-generated fake credentials
  - Real vault remains locked
  - All edit/delete actions are silently no-ops
  - Intrusion timestamp + count written to intrusion_log
  - Reset only on successful master password entry

#7. Freeze Mode Design

Trigger: Ctrl+Shift+F (global hotkey via keyboard library)

Activation:
  1. MainWindow.setWindowTitle("Calculator")
  2. CalculatorOverlay widget shown over entire window
  3. Real vault content hidden (not destroyed)
  4. Vault key remains in memory

Deactivation:
  1. User types PIN digits on calculator overlay
  2. PIN verified: "1234" followed by "="
  3. Overlay hidden; real vault restored
  4. Window title restored to "FrostVault — by IceLegends"

Note: Freeze Mode does NOT lock the vault — it is a visual
      camouflage layer, not a security boundary.

#8. Residual Risks

RiskSeverityLikelihoodNotes
Memory forensics on unlocked sessionHighLowPython bytes not securely wiped
Swap file contains decrypted dataMediumLowWindows page file mitigation: none
Keylogger captures master passwordCriticalMediumOut of scope for this version
Attacker knows to press "=" after "1234"MediumLowFreeze Mode PIN is hardcoded
SQLite WAL file contains plaintextLowVery LowWAL only contains ciphertext
Side-channel timing on Argon2 verifyLowVery Lowargon2-cffi uses constant-time compare
~ End of Document ~