Storing passwords, API tokens, and private SSH keys in unencrypted plaintext files, sticky notes, or shell environment scripts creates an existential security hazard. If malware gains user-level execution on your machine or an attacker exfiltrates your disk image, plaintext credentials result in total compromise.
In this guide, you will build a secure, zero-knowledge command-line password manager using Python's industry-standard cryptography library. We will utilize PBKDF2-HMAC-SHA256 for key derivation with 600,000 hashing rounds and Fernet AES-256 GCM authenticated encryption to guarantee confidentiality and tamper-proof integrity.
Cryptographic Vault Architecture
Prerequisites & Installation
cryptography provides industry-standard primitives including PBKDF2HMAC and Fernet AES-256.
pip install cryptography
| Component | Standard / Algorithm | Security Specification | Defensive Role |
|---|---|---|---|
| Key Derivation Function | PBKDF2-HMAC-SHA256 | 600,000 iterations + 128-bit random salt | Slows down GPU/ASIC brute-force dictionary attacks on master password |
| Symmetric Cipher | AES-256 GCM (Fernet) | 256-bit encryption key + HMAC verification | Provides authenticated encryption (confidentiality and anti-tamper) |
| Salt Generation | os.urandom(16) | Cryptographically secure pseudo-random number generator (CSPRNG) | Prevents rainbow table lookup attacks |
| Vault Persistence | JSON over Encrypted Binary Payload | Zero plaintext stored on disk | Guarantees zero-knowledge storage |
How Authenticated Key Derivation Works
A master password cannot be directly used as an AES encryption key because human passwords lack sufficient entropy. A Key Derivation Function (KDF) stretches the user's password into a fixed 256-bit cryptographic key.
By enforcing 600,000 rounds of SHA-256 hashing alongside a unique random salt, an attacker attempting to brute-force the master password must compute hundreds of thousands of hashes for every single password guess.
Building the Encrypted Vault Application in Python
import base64
import os
import json
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
class SecurePasswordVault:
"""Zero-knowledge encrypted password vault using AES-256 GCM."""
def __init__(self, vault_filepath="vault.enc"):
self.vault_filepath = vault_filepath
def _derive_key(self, master_password: str, salt: bytes) -> bytes:
"""Derives a 256-bit key from master passphrase using 600,000 PBKDF2 iterations."""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=600_000,
)
return base64.urlsafe_b64encode(kdf.derive(master_password.encode('utf-8')))
def save_vault(self, data: dict, master_password: str):
"""Encrypt and write vault data to disk."""
salt = os.urandom(16)
key = self._derive_key(master_password, salt)
fernet = Fernet(key)
raw_json = json.dumps(data).encode('utf-8')
encrypted_payload = fernet.encrypt(raw_json)
# Store salt (16 bytes) followed by ciphertext
with open(self.vault_filepath, "wb") as f:
f.write(salt + encrypted_payload)
print(f"[Vault Saved] Encrypted {len(data)} credential records.")
def load_vault(self, master_password: str) -> dict:
"""Read and decrypt vault data from disk."""
if not os.path.exists(self.vault_filepath):
return {}
with open(self.vault_filepath, "rb") as f:
content = f.read()
salt = content[:16]
encrypted_payload = content[16:]
key = self._derive_key(master_password, salt)
fernet = Fernet(key)
decrypted_json = fernet.decrypt(encrypted_payload).decode('utf-8')
return json.loads(decrypted_json)
if __name__ == "__main__":
vault = SecurePasswordVault("my_secrets.enc")
pwd = "MasterPassword!2026Secure"
# Store credentials
secrets = {
"github.com": {"user": "alex_dev", "token": "ghp_9847128947192471"},
"aws_console": {"user": "admin", "token": "AKIAIOSFODNN7EXAMPLE"}
}
vault.save_vault(secrets, pwd)
# Decrypt and retrieve
unlocked = vault.load_vault(pwd)
print("Decrypted Vault Contents:", unlocked)
Operational Security and Memory Safety
- Prevent Memory Swapping: Sensitive passphrases stored in Python string objects remain in memory until garbage collected. For maximum security, avoid printing plaintext credentials to stdout.
- Never Hardcode Salts: Each time you save or re-encrypt the vault, generate a fresh random salt via os.urandom(16) to ensure identical credentials produce completely different ciphertexts.
- Regular Vault Backups: Because AES-256 GCM is authenticated, any byte corruption on disk will cause decryption to fail entirely. Keep timestamped encrypted backups.
Frequently Asked Questions
Q: What happens if I forget the master password?
A: Because this is a zero-knowledge architecture with no backdoor or recovery key, the data cannot be decrypted by anyone if the master password is lost.
Q: Is Fernet quantum-resistant?
A: Fernet utilizes AES-256 symmetric encryption, which offers 128-bit quantum security against Grover's algorithm and remains secure against foreseeable quantum threats.
0 Comments