This document describes PassKey’s cryptographic design, threat model, and security properties in detail. It is intended for security-conscious users and auditors.
For a shorter overview, see SECURITY.md in the repository root.
PassKey is a zero-knowledge system:
char[] or ReadOnlySpan<char> and cleared with Array.Clear immediately after the KDF computation.| Component | Algorithm | Parameters |
|---|---|---|
| Vault encryption | AES-256-GCM | 256-bit key, 96-bit random nonce, 128-bit authentication tag |
| Key derivation (new vaults) | Argon2id | Memory: 64 MB, Iterations: 3, Parallelism: 4 |
| Key derivation (legacy vaults) | PBKDF2-SHA256 | 600,000 iterations |
| IPC session key | ECDH P-256 + HKDF-SHA256 | Ephemeral key pair per connection |
| IPC message encryption | AES-256-GCM | Unique nonce per message |
All parameters meet or exceed OWASP 2023 recommendations for password hashing and authenticated encryption.
PassKey uses a Key Encryption Key (KEK) / Data Encryption Key (DEK) split:
Master Password + Salt
│
▼
┌────────┐
│ KDF │ (Argon2id or PBKDF2-SHA256)
└────────┘
│
▼
KEK (Key Encryption Key — 32 bytes, ephemeral)
│
▼
┌─────────────┐
│ AES-GCM │ Unwrap
│ Decrypt │──────────► DEK (Data Encryption Key — 32 bytes)
└─────────────┘ │
▼
┌─────────────┐
│ AES-GCM │ Decrypt
│ Decrypt │──────────► Vault (JSON)
└─────────────┘
VaultMetadata database table.VaultData database table.The DEK is stored in a PinnedSecureBuffer:
GCHandle.Alloc(array, GCHandleType.Pinned) to prevent the garbage collector from copying it to a new location (which would leave the old copy in memory).CryptographicOperations.ZeroMemory is called on the array before the pin is released. This method is guaranteed by the .NET runtime not to be optimised away by the JIT compiler, unlike Array.Clear.The KEK is never stored. It exists only as a local variable during the unlock operation and is eligible for GC immediately after.
The vault is stored in a SQLite database at %LOCALAPPDATA%\PassKey\vault.db with three tables:
| Table | Contents | Encrypted? |
|---|---|---|
VaultMetadata |
KDF algorithm, salt, iteration count, encrypted DEK blob | DEK blob is encrypted; metadata fields are plaintext |
VaultData |
Single AES-GCM encrypted blob containing the entire vault | Yes |
ActivityLog |
Audit trail (add/edit/delete actions with timestamps) | No |
[Nonce (12 bytes)] [Ciphertext (variable)] [Authentication Tag (16 bytes)]
.pkbak)Offset Length Description
0 4 Magic bytes: "PKBK" (0x504B424B)
4 1 Format version (currently 0x01)
5 32 Argon2id salt
37 12 AES-GCM nonce
49 var Encrypted vault payload (ciphertext + 16-byte auth tag)
The backup password (which may differ from the vault master password) is fed through Argon2id with the embedded salt to derive a 32-byte key, which decrypts the payload.
Communication between the browser extension and PassKey Desktop follows this path:
Extension ──(Native Messaging / stdio)──► BrowserHost ──(Named Pipe)──► Desktop
When a password is copied to the clipboard:
DataPackage.ClipboardContentOptions.IsAllowedInHistory is set to false, which prevents the entry from appearing in Windows clipboard history (Win+V). Requires Windows 10 version 1809+.ClipboardContentOptions.IsRoamable is set to false, preventing clipboard sync across devices.PassKey is designed to be secure against remote attackers and offline vault theft. It does not protect against:
| Threat | Why |
|---|---|
| Malware with admin/kernel privileges | Can read process memory, intercept keystrokes, or modify the app binary |
| Keyloggers | Can capture the master password as it is typed |
| Physical access while unlocked | The vault is decrypted in memory; anyone with screen access can read it |
| Weak master passwords | While the DEK itself is random, a weak master password allows brute-forcing the KEK via KDF |
| Screen capture while unlocked | The revealed password is visible in plaintext in the UI |