Password managers protect your data through three layers: your master password is transformed into an encryption key via a key derivation function (like Argon2id or PBKDF2 with 100,000+ iterations), that key encrypts your vault with AES-256-GCM before anything leaves your device, and zero-knowledge architecture means the server never possesses your decryption key. Even if the server is breached, attackers see only encrypted blobs they cannot decrypt without your master password. Below is a detailed breakdown of each mechanism with code examples.
The Core Problem: Encrypting at Rest
When you save a password locally or sync it to the cloud, that data must be encrypted. The challenge is straightforward: you need to encrypt data that only you can decrypt, without storing the decryption key somewhere that could be compromised.
Modern password managers solve this using derived key encryption. Instead of storing a static encryption key, your master password undergoes a transformation to produce a cryptographic key. This key then encrypts and decrypts your vault.
Key Derivation: From Password to Key
Your master password never leaves your device. Instead, it passes through a key derivation function (KDF) to produce a cryptographic key. The most common KDFs are Argon2id, PBKDF2, and bcrypt.
Here’s how this works conceptually in pseudocode:
import hashlib
import os
def derive_key(master_password: str, salt: bytes, iterations: int = 100000) -> bytes:
"""Derive an encryption key from a master password using PBKDF2."""
return hashlib.pbkdf2_hmac(
'sha256',
master_password.encode('utf-8'),
salt,
iterations,
dklen=32 # 256-bit key
)
# Generate a random salt
salt = os.urandom(16)
# Derive key from your master password
master_password = "your-master-password-here"
encryption_key = derive_key(master_password, salt, iterations=100000)
The salt prevents rainbow table attacks, and the high iteration count makes brute-force attempts computationally expensive. Modern password managers typically use 100,000 to 600,000 iterations for PBKDF2, or configure Argon2id with memory-hard parameters.
Zero-Knowledge Architecture
A critical concept in password manager security is zero-knowledge. This means the service provider—whether 1Password, Bitwarden, or KeePass—cannot read your stored passwords. The encryption and decryption happen entirely on your device.
Here’s how zero-knowledge works in practice:
Encryption happens on your device before any data leaves it. The server stores only ciphertext, never plaintext. When you unlock your vault, the decryption key is derived from your master password locally—the server is not involved.
This architecture protects you even if the server is compromised. An attacker with access to the database sees only random-looking encrypted data.
// Conceptual example of client-side encryption
async function encryptVault(plaintext, masterPassword) {
// Derive key from master password (happens locally)
const key = await deriveKey(masterPassword);
// Generate random IV for each encryption
const iv = crypto.getRandomValues(new Uint8Array(12));
// Encrypt the vault data
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
key,
plaintext
);
// Return encrypted data + IV (IV is not secret, just unique)
return { iv: Array.from(iv), data: Array.from(new Uint8Array(encrypted)) };
}
Encryption Algorithms: AES-256-GCM
Most password managers use AES-256-GCM for symmetric encryption. The GCM mode provides both confidentiality (you can’t read the data without the key) and authenticity (you can detect if the data was tampered with).
AES-256-GCM is the gold standard because: AES-256-GCM uses 256-bit keys for a substantial security margin, includes built-in integrity checking through GCM mode, and benefits from hardware acceleration on most modern processors.
When you sync your vault to the cloud, the encrypted blob looks something like this:
vault.encrypted = AES-256-GCM encrypt(
key=derived_from_master_password,
plaintext=JSON.stringify(vault_data),
iv=random_12_bytes
)
The Unlock Flow: Putting It Together
When you enter your master password to unlock your password manager, here’s the complete flow:
You type your master password on your device. Your client fetches the salt from the server (the salt is public), then the KDF transforms your password and salt into an encryption key. Your client decrypts the vault locally using that key, and the unlocked vault remains available until you lock or close the app.
# Complete unlock flow example
def unlock_vault(master_password: str, encrypted_vault: bytes, salt: bytes) -> dict:
# Step 1: Derive the encryption key
encryption_key = derive_key(master_password, salt)
# Step 2: Decrypt the vault (simplified - real implementations handle IV separately)
iv = encrypted_vault[:12]
ciphertext = encrypted_vault[12:]
# Step 3: Decrypt using AES-GCM
plaintext = aes_gcm_decrypt(encryption_key, iv, ciphertext)
# Step 4: Parse and return vault contents
return json.loads(plaintext)
Security Considerations for Developers
If you’re building applications that interact with password managers or implementing similar security patterns, keep these principles in mind:
- Never store the master password: Only store derived keys or salted password hashes
- Use authenticated encryption: AES-GCM or ChaCha20-Poly1305, never AES-CTR or AES-ECB
- Implement proper key derivation: Use Argon2id when possible, with memory-hard parameters
- Handle memory carefully: Clear sensitive data from memory after use
- Validate ciphertext integrity: Reject decryption if authentication fails
Common Misconceptions
A few clarifications that help when evaluating password manager security:
Cloud sync does not mean the provider stores your data in plaintext—it stays encrypted on their servers. Your master password is never sent anywhere; zero-knowledge means the server never sees it. Because your encryption key is derived from your master password, a compromised master password compromises everything.
Summary
Password managers achieve security through layered mechanisms: client-side encryption ensures your data never leaves your device in plaintext, key derivation transforms your master password into a secure cryptographic key, and authenticated encryption protects against tampering. Understanding these fundamentals helps you evaluate different tools and configure them appropriately.
The security model ultimately rests on your master password being strong and unique. Even the most sophisticated encryption cannot protect against a compromised master password.
Related Reading
Built by theluckystrike — More at zovo.one