Build a Password Manager in Python
Introduction
Password managers are essential tools in today's digital world. They help you create, store, and manage unique, strong passwords for all your accounts without memorizing them.
In this tutorial, you'll build a fully functional command-line password manager in Python. You'll learn about encryption, secure storage, and how to build a practical security tool.
A command-line password manager with:
- Encrypted password storage
- Master password protection
- Password generator
- Add, view, update, and delete passwords
- Search functionality
- Cryptography with the cryptography library
- Fernet encryption (symmetric encryption)
- Secure password hashing
- JSON file storage
- Command-line interface design
How Password Managers Work
Before we start coding, let's understand the core concepts behind password managers.
The Core Concept
A password manager stores your passwords in an encrypted vault. You unlock the vault with a single "master password," and the manager handles the rest.
Encryption Basics
Password managers use encryption to protect your data:
- Symmetric Encryption: The same key encrypts and decrypts data
- Master Password: Derives the encryption key through hashing
- Salt: Random data added before hashing to prevent attacks
Without encryption, if someone gains access to your password file, they can see all your passwords. With encryption, they only see scrambled nonsense that can't be decrypted without the master password.
Project Overview
Our password manager will have these features:
Core Features
- Master Password: Single password to unlock everything
- Add Passwords: Store new login credentials
- View Passwords: Retrieve stored passwords
- Generate Passwords: Create strong random passwords
- Search: Find passwords quickly
- Delete: Remove old entries
Technical Design
- Language: Python 3
- Encryption: Fernet (symmetric)
- Storage: JSON file
- Interface: Command-line (CLI)
Prerequisites
Before starting, ensure you have:
- Python 3.8+ installed
- Code editor (VS Code recommended)
- Basic Python knowledge
Setting Up the Project
Let's set up our project and install the necessary library.
# Create project directory
mkdir password-manager
cd password-manager
# Create virtual environment (recommended)
python -m venv venv
# Activate on Windows:
venv\Scripts\activate
# Activate on Mac/Linux:
source venv/bin/activate
# Install cryptography library
pip install cryptography
# What cryptography provides:
# - Fernet: Symmetric encryption
# - Generates keys from passwords
# - Handles all the complex math
Project Structure
password-manager/
├── password_manager/
│ ├── __init__.py
│ ├── crypto.py # Encryption/decryption
│ ├── vault.py # Password storage
│ ├── generator.py # Password generation
│ └── main.py # CLI interface
├── venv/
└── passwords.json # Encrypted password file
Encryption Fundamentals
Let's create the encryption module that handles all cryptographic operations.
# password_manager/crypto.py
import cryptography
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2
import base64
import os
class CryptoManager:
"""Handles all encryption and decryption operations"""
def __init__(self, master_password):
self.master_password = master_password
self.salt = self.get_or_create_salt()
self.key = self.derive_key(master_password, self.salt)
self.cipher = Fernet(self.key)
def get_or_create_salt(self):
"""Get existing salt or create new one"""
salt_file = "salt.key"
if os.path.exists(salt_file):
with open(salt_file, "rb") as f:
return f.read()
# Create new random salt
salt = os.urandom(16)
with open(salt_file, "wb") as f:
f.write(salt)
return salt
def derive_key(self, password, salt):
"""Derive encryption key from password using PBKDF2"""
kdf = PBKDF2(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
)
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
return key
def encrypt(self, data):
"""Encrypt data (string or bytes)"""
if isinstance(data, str):
data = data.encode()
return self.cipher.encrypt(data)
def decrypt(self, encrypted_data):
"""Decrypt data back to string"""
decrypted = self.cipher.decrypt(encrypted_data)
return decrypted.decode()
def verify_password(self, password):
"""Verify if password matches (creates new CryptoManager)"""
try:
test_crypto = CryptoManager(password)
# Try to decrypt a known value
test_data = "test"
encrypted = test_crypto.encrypt(test_data)
self.decrypt(encrypted)
return True
except:
return False
We use PBKDF2 (Password-Based Key Derivation Function 2):
- Salt: Random data that makes each key unique
- Iterations (100,000): Makes key generation slow to prevent brute-force attacks
- SHA256: Cryptographically secure hash algorithm
This means even if someone knows your master password, they'd need years to crack it!
Building the Password Vault
Now let's create the vault module that stores and retrieves passwords.
# password_manager/vault.py
import json
import os
from datetime import datetime
class PasswordVault:
"""Handles storing and retrieving passwords"""
def __init__(self, crypto_manager, vault_file="passwords.json"):
self.crypto = crypto_manager
self.vault_file = vault_file
self.passwords = self.load_vault()
def load_vault(self):
"""Load passwords from encrypted file"""
if not os.path.exists(self.vault_file):
return {}
try:
with open(self.vault_file, "rb") as f:
encrypted_data = f.read()
# Decrypt and parse JSON
decrypted = self.crypto.decrypt(encrypted_data)
return json.loads(decrypted)
except Exception as e:
print(f"Error loading vault: {e}")
return {}
def save_vault(self):
"""Save passwords to encrypted file"""
try:
# Convert to JSON and encrypt
json_data = json.dumps(self.passwords, indent=2)
encrypted = self.crypto.encrypt(json_data)
with open(self.vault_file, "wb") as f:
f.write(encrypted)
return True
except Exception as e:
print(f"Error saving vault: {e}")
return False
def add_password(self, service, username, password, notes=""):
"""Add a new password entry"""
entry = {
"username": username,
"password": password,
"notes": notes,
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat()
}
self.passwords[service.lower()] = entry
return self.save_vault()
def get_password(self, service):
"""Retrieve password for a service"""
service = service.lower()
if service in self.passwords:
return self.passwords[service]
return None
def update_password(self, service, username=None, password=None, notes=None):
"""Update an existing password entry"""
service = service.lower()
if service not in self.passwords:
return False
entry = self.passwords[service]
if username: entry["username"] = username
if password: entry["password"] = password
if notes is not None: entry["notes"] = notes
entry["updated_at"] = datetime.now().isoformat()
return self.save_vault()
def delete_password(self, service):
"""Delete a password entry"""
service = service.lower()
if service in self.passwords:
del self.passwords[service]
return self.save_vault()
return False
def list_services(self):
"""List all services in the vault"""
return list(self.passwords.keys())
def search(self, query):
"""Search for services matching query"""
query = query.lower()
results = {}
for service, data in self.passwords.items():
if query in service or query in data.get("username", "").lower():
results[service] = data
return results
Never store passwords in plain text! Our vault encrypts all data before saving. Even if someone steals the passwords.json file, they can't read the passwords without the master password.
Creating a Password Generator
Let's add a secure password generator that creates strong, random passwords.
# password_manager/generator.py
import random
import string
class PasswordGenerator:
"""Generate secure random passwords"""
def __init__(self):
self.lowercase = string.ascii_lowercase
self.uppercase = string.ascii_uppercase
self.digits = string.digits
self.symbols = string.punctuation
def generate(self, length=16, use_uppercase=True,
use_digits=True, use_symbols=True):
"""Generate a random password with specified requirements"""
# Start with required character types
chars = self.lowercase
if use_uppercase:
chars += self.uppercase
if use_digits:
chars += self.digits
if use_symbols:
chars += self.symbols
# Generate password ensuring it has at least one of each type
password = []
# Always include at least one lowercase
password.append(random.choice(self.lowercase))
if use_uppercase:
password.append(random.choice(self.uppercase))
if use_digits:
password.append(random.choice(self.digits))
if use_symbols:
password.append(random.choice(self.symbols))
# Fill the rest with random characters
while len(password) < length:
password.append(random.choice(chars))
# Shuffle to randomize positions
random.shuffle(password)
return ''.join(password)
def calculate_strength(self, password):
"""Calculate password strength (0-100)"""
score = 0
# Length score
if len(password) >= 8: score += 20
if len(password) >= 12: score += 20
if len(password) >= 16: score += 10
# Character variety
if any(c in self.lowercase for c in password): score += 15
if any(c in self.uppercase for c in password): score += 15
if any(c in self.digits for c in password): score += 10
if any(c in self.symbols for c in password): score += 10
return min(score, 100)
# Test the generator
if __name__ == "__main__":
gen = PasswordGenerator()
password = gen.generate(length=16)
strength = gen.calculate_strength(password)
print(f"Generated: {password}")
print(f"Strength: {strength}/100")
Master Password System
Now let's create the main entry point that ties everything together.
# password_manager/main.py
from password_manager.crypto import CryptoManager
from password_manager.vault import PasswordVault
from password_manager.generator import PasswordGenerator
import getpass
import os
class PasswordManager:
"""Main password manager interface"""
def __init__(self):
self.crypto = None
self.vault = None
self.generator = PasswordGenerator()
def create_master_password(self, password):
"""Create new master password (first time setup)"""
self.crypto = CryptoManager(password)
self.vault = PasswordVault(self.crypto)
print("Master password created successfully!")
def unlock(self, password):
"""Unlock the vault with master password"""
# Check if vault exists
if not os.path.exists("passwords.json"):
print("No vault found. Creating new one...")
self.create_master_password(password)
return True
# Try to unlock existing vault
try:
self.crypto = CryptoManager(password)
self.vault = PasswordVault(self.crypto)
# Verify password by trying to load data
_ = self.vault.passwords
print("Vault unlocked successfully!")
return True
except Exception as e:
print(f"Wrong master password: {e}")
return False
def add_entry(self, service, username, password, notes=""):
"""Add a new password entry"""
return self.vault.add_password(service, username, password, notes)
def get_entry(self, service):
"""Get password entry"""
return self.vault.get_password(service)
def list_entries(self):
"""List all services"""
return self.vault.list_services()
def delete_entry(self, service):
"""Delete a password entry"""
return self.vault.delete_password(service)
Building the CLI
Now let's create the command-line interface that users will interact with.
# password_manager/cli.py
from password_manager.main import PasswordManager
import getpass
import sys
def main():
"""Main CLI interface"""
pm = PasswordManager()
print("=" * 50)
print(" PASSWORD MANAGER")
print("=" * 50)
print()
# Get master password
print("Enter your master password:")
master_password = getpass.getpass("> ")
# Try to unlock vault
if not pm.unlock(master_password):
sys.exit(1)
print()
print("Commands:")
print(" add - Add a new password")
print(" get - Get a password")
print(" list - List all services")
print(" generate - Generate a password")
print(" delete - Delete a password")
print(" quit - Exit")
print()
while True:
command = input("Enter command: ").strip().lower()
if command == "quit" or command == "q":
print("Goodbye!")
break
elif command == "add" or command == "a":
service = input("Service name: ").strip()
username = input("Username: ").strip()
password = getpass.getpass("Password (leave empty to generate): ")
if not password:
password = pm.generator.generate()
print(f"Generated password: {password}")
notes = input("Notes (optional): ").strip()
if pm.add_entry(service, username, password, notes):
print("✓ Password saved!")
else:
print("✗ Error saving password")
elif command == "get" or command == "g":
service = input("Service name: ").strip()
entry = pm.get_entry(service)
if entry:
print("\nService:", service)
print("Username:", entry["username"])
print("Password:", entry["password"])
if entry.get("notes"):
print("Notes:", entry["notes"])
print()
else:
print("✗ Service not found")
elif command == "list" or command == "l":
services = pm.list_entries()
if services:
print("\nStored services:")
for s in services:
print(" -", s)
print()
else:
print("No passwords stored yet.")
elif command == "generate" or command == "gen":
length = int(input("Password length (default 16): ") or "16")
password = pm.generator.generate(length=length)
strength = pm.generator.calculate_strength(password)
print(f"\nGenerated: {password}")
print(f"Strength: {strength}/100")
print()
elif command == "delete" or command == "d":
service = input("Service to delete: ").strip()
confirm = input(f"Delete {service}? (yes/no): ").strip().lower()
if confirm == "yes":
if pm.delete_entry(service):
print("✓ Deleted!")
else:
print("✗ Not found")
else:
print("Unknown command. Try: add, get, list, generate, delete, or quit")
if __name__ == "__main__":
main()
Don't forget to create empty __init__.py files in your password_manager directory to make Python treat it as a package:
# password_manager/__init__.py
# password_manager/crypto/__init__.py (if using subdirectories)
Testing the Application
Let's test our password manager.
Running the Application
# Run the password manager
python -m password_manager.cli
# Or if you set up the package correctly
python password_manager/cli.py
Testing Steps
- Run the application
- Enter a master password (first time creates new vault)
- Use "generate" to create a password
- Use "add" to store credentials
- Use "list" to see all services
- Use "get" to retrieve a password
- Run again and try wrong password
- ✓ First run creates salt.key and passwords.json
- ✓ Master password encrypts/decrypts correctly
- ✓ Password generator creates strong passwords
- ✓ Add/get/list/delete all work
- ✓ Wrong password fails to unlock
Enhancements
Here are ideas for expanding your password manager:
- Import/Export: Import from other password managers
- Categories: Organize passwords by category
- Password History: Keep old passwords
- Auto-lock: Lock after inactivity
- Web Interface: Add a GUI
- Cloud Sync: Sync across devices
- Breach Checking: Check if passwords were compromised
This is a learning project. For real-world use, consider established password managers like Bitwarden, 1Password, or KeepassXC. They have been thoroughly audited and have additional security features.
Summary
Congratulations! You've built a complete password manager in Python.
What You Built
- Encryption System: Fernet encryption with PBKDF2 key derivation
- Password Vault: Secure storage with JSON encryption
- Password Generator: Configurable strong password creation
- CLI Interface: User-friendly command-line interface
Key Concepts Learned
- Symmetric encryption with Fernet
- Password-based key derivation
- Secure password storage
- Random password generation
- CLI application design
Next Steps
Try these tutorials next:
- Build a Web Scraper in Python
- Build a REST API with Node.js
- Build a Blog CMS