Building a Custom Password Manager with Encryption
Building a Custom Password Manager with Encryption
In an era where password breaches are increasingly common, understanding how password managers work under the hood is crucial for cybersecurity professionals. This project will guide you through building your own password manager with enterprise-grade encryption.
Project Overview
We’ll create a command-line password manager that implements:
- AES-256 encryption for password storage
- PBKDF2 for key derivation
- Salt generation for additional security
- Secure password generation with customizable complexity
- Master password protection with multiple authentication factors
Security Requirements
Encryption Standards
- AES-256-GCM: Authenticated encryption for password data
- PBKDF2-SHA256: Key derivation with 100,000+ iterations
- Cryptographically secure random: For salt and IV generation
- Memory protection: Secure handling of sensitive data
Architecture Principles
- Zero-knowledge architecture: Master password never stored
- Local storage only: No cloud dependencies by default
- Minimal attack surface: Command-line interface only
- Secure defaults: Conservative security settings
Technology Stack
Core Dependencies
# requirements.txt
cryptography==41.0.7
argon2-cffi==23.1.0
pyperclip==1.8.2
rich==13.7.0
typer==0.9.0
Development Tools
- Python 3.9+: Modern Python features and security updates
- Poetry: Dependency management and virtual environments
- pytest: Unit testing framework
- black: Code formatting
- mypy: Static type checking
Implementation Phases
Phase 1: Core Encryption Module
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
import os
import base64
class PasswordCrypto:
def __init__(self, master_password: str, salt: bytes = None):
self.salt = salt or os.urandom(32)
self.key = self._derive_key(master_password)
def _derive_key(self, password: str) -> bytes:
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=self.salt,
iterations=100000,
)
return kdf.derive(password.encode())
def encrypt(self, plaintext: str) -> str:
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(self.key), modes.GCM(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext.encode()) + encryptor.finalize()
return base64.b64encode(iv + encryptor.tag + ciphertext).decode()
def decrypt(self, encrypted_data: str) -> str:
data = base64.b64decode(encrypted_data)
iv = data[:16]
tag = data[16:32]
ciphertext = data[32:]
cipher = Cipher(algorithms.AES(self.key), modes.GCM(iv, tag))
decryptor = cipher.decryptor()
return (decryptor.update(ciphertext) + decryptor.finalize()).decode()
Phase 2: Password Storage Backend
import json
import sqlite3
from datetime import datetime
from typing import Dict, List, Optional
class PasswordDatabase:
def __init__(self, db_path: str = "passwords.db"):
self.db_path = db_path
self._init_database()
def _init_database(self):
conn = sqlite3.connect(self.db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS passwords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
service TEXT NOT NULL,
username TEXT NOT NULL,
encrypted_password TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(service, username)
)
""")
conn.commit()
conn.close()
def store_password(self, service: str, username: str, encrypted_password: str):
conn = sqlite3.connect(self.db_path)
conn.execute("""
INSERT OR REPLACE INTO passwords
(service, username, encrypted_password, updated_at)
VALUES (?, ?, ?, ?)
""", (service, username, encrypted_password, datetime.now()))
conn.commit()
conn.close()
def get_password(self, service: str, username: str) -> Optional[str]:
conn = sqlite3.connect(self.db_path)
cursor = conn.execute("""
SELECT encrypted_password FROM passwords
WHERE service = ? AND username = ?
""", (service, username))
result = cursor.fetchone()
conn.close()
return result[0] if result else None
Phase 3: Secure Password Generation
import secrets
import string
from typing import Set
class PasswordGenerator:
def __init__(self):
self.lowercase = string.ascii_lowercase
self.uppercase = string.ascii_uppercase
self.digits = string.digits
self.symbols = "!@#$%^&*()_+-=[]{}|;:,.<>?"
def generate(self,
length: int = 16,
use_uppercase: bool = True,
use_lowercase: bool = True,
use_digits: bool = True,
use_symbols: bool = True,
exclude_ambiguous: bool = True) -> str:
charset = ""
required_chars = []
if use_lowercase:
chars = self.lowercase
if exclude_ambiguous:
chars = chars.replace('l', '').replace('o', '')
charset += chars
required_chars.append(secrets.choice(chars))
if use_uppercase:
chars = self.uppercase
if exclude_ambiguous:
chars = chars.replace('I', '').replace('O', '')
charset += chars
required_chars.append(secrets.choice(chars))
if use_digits:
chars = self.digits
if exclude_ambiguous:
chars = chars.replace('0', '').replace('1', '')
charset += chars
required_chars.append(secrets.choice(chars))
if use_symbols:
charset += self.symbols
required_chars.append(secrets.choice(self.symbols))
# Generate remaining characters
remaining_length = length - len(required_chars)
random_chars = [secrets.choice(charset) for _ in range(remaining_length)]
# Combine and shuffle
password_chars = required_chars + random_chars
secrets.SystemRandom().shuffle(password_chars)
return ''.join(password_chars)
def check_strength(self, password: str) -> Dict[str, any]:
"""Analyze password strength and provide feedback"""
score = 0
feedback = []
if len(password) >= 12:
score += 2
elif len(password) >= 8:
score += 1
else:
feedback.append("Password should be at least 8 characters long")
if any(c.islower() for c in password):
score += 1
else:
feedback.append("Add lowercase letters")
if any(c.isupper() for c in password):
score += 1
else:
feedback.append("Add uppercase letters")
if any(c.isdigit() for c in password):
score += 1
else:
feedback.append("Add numbers")
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
score += 1
else:
feedback.append("Add special characters")
strength_levels = {
0-2: "Very Weak",
3-4: "Weak",
5: "Fair",
6: "Strong",
7: "Very Strong"
}
strength = next(level for range_key, level in strength_levels.items()
if score in range(range_key, range_key + 1))
return {
"score": score,
"strength": strength,
"feedback": feedback
}
Phase 4: Command-Line Interface
import typer
from rich.console import Console
from rich.table import Table
from rich.prompt import Prompt
import pyperclip
app = typer.Typer()
console = Console()
@app.command()
def add(service: str, username: str):
"""Add a new password entry"""
master_password = Prompt.ask("Master password", password=True)
# Option to generate or enter password
if typer.confirm("Generate a secure password?"):
generator = PasswordGenerator()
password = generator.generate()
console.print(f"Generated password: {password}", style="green")
pyperclip.copy(password)
console.print("Password copied to clipboard!", style="yellow")
else:
password = Prompt.ask("Enter password", password=True)
# Encrypt and store
crypto = PasswordCrypto(master_password)
encrypted = crypto.encrypt(password)
db = PasswordDatabase()
db.store_password(service, username, encrypted)
console.print("Password stored successfully!", style="green")
@app.command()
def get(service: str, username: str):
"""Retrieve a password"""
master_password = Prompt.ask("Master password", password=True)
db = PasswordDatabase()
encrypted_password = db.get_password(service, username)
if not encrypted_password:
console.print("Password not found!", style="red")
return
try:
crypto = PasswordCrypto(master_password)
password = crypto.decrypt(encrypted_password)
pyperclip.copy(password)
console.print("Password copied to clipboard!", style="green")
except Exception:
console.print("Invalid master password!", style="red")
if __name__ == "__main__":
app()
Security Considerations
Key Management
- Master password: Never stored, only used for key derivation
- Salt storage: Stored alongside encrypted data
- Key rotation: Implement periodic key updates
- Memory clearing: Explicitly clear sensitive variables
Threat Modeling
- Memory dumps: Protect against RAM analysis
- Swap files: Disable swap or use encrypted swap
- Process isolation: Run in separate security context
- File permissions: Restrict database access
Audit and Compliance
- Access logging: Track all password access attempts
- Integrity checking: Verify database integrity
- Backup strategies: Secure encrypted backups
- Disaster recovery: Master password recovery procedures
Testing Strategy
Unit Tests
import pytest
from password_manager import PasswordCrypto, PasswordGenerator
def test_encryption_roundtrip():
crypto = PasswordCrypto("test_master_password")
plaintext = "supersecretpassword123!"
encrypted = crypto.encrypt(plaintext)
decrypted = crypto.decrypt(encrypted)
assert decrypted == plaintext
def test_different_salts_different_output():
password = "same_password"
crypto1 = PasswordCrypto("master", os.urandom(32))
crypto2 = PasswordCrypto("master", os.urandom(32))
encrypted1 = crypto1.encrypt(password)
encrypted2 = crypto2.encrypt(password)
assert encrypted1 != encrypted2
def test_password_generation_strength():
generator = PasswordGenerator()
password = generator.generate(length=16)
strength = generator.check_strength(password)
assert strength["score"] >= 6 # Should be "Strong" or better
Integration Tests
- Database operations: Test CRUD operations
- CLI interface: Test all command combinations
- Error handling: Test invalid inputs and edge cases
- Performance: Benchmark encryption/decryption speed
Deployment and Distribution
Packaging
# Build standalone executable
pyinstaller --onefile --name password-manager main.py
# Create distributable package
python setup.py sdist bdist_wheel
Security Hardening
- Code signing: Sign executables for integrity
- Static analysis: Use bandit for security scanning
- Dependency scanning: Check for vulnerable dependencies
- Penetration testing: Professional security assessment
Advanced Features
Multi-Factor Authentication
- TOTP integration: Time-based one-time passwords
- Hardware tokens: YubiKey support
- Biometric authentication: Fingerprint verification
Enterprise Features
- Team sharing: Secure password sharing mechanisms
- Role-based access: Different permission levels
- Audit trails: Comprehensive logging
- API integration: REST API for automation
Cloud Sync (Optional)
- End-to-end encryption: Client-side encryption only
- Zero-knowledge sync: Server never sees plaintext
- Conflict resolution: Handle concurrent modifications
- Offline capability: Full functionality without internet
Conclusion
Building a custom password manager provides deep insights into:
- Modern cryptography: Practical application of encryption
- Security architecture: Designing secure systems
- Key management: Protecting cryptographic keys
- Threat modeling: Understanding attack vectors
This project demonstrates that with proper security practices, it’s possible to build enterprise-grade security tools. However, for production use, consider established solutions like Bitwarden or 1Password that have undergone extensive security audits.
The knowledge gained from this project applies to many other security applications:
- Secure communication systems
- Data protection tools
- Authentication systems
- Cryptographic protocols
Next Steps
- Extend the feature set: Add secure notes, file encryption
- Improve the UI: Build a GUI version with Electron or Tauri
- Mobile apps: Create companion mobile applications
- Browser extensions: Integrate with web browsers
- Security audit: Have the code professionally reviewed
Keep your implementation updated - regularly patch dependencies, review security practices, and stay current with cryptographic standards. Security requirements evolve as threats do.
Want to practice on vulnerable systems first? Check out our vulnerable applications deployment guide to set up a safe testing environment.