← Back to Tutorials
Python

Build a Password Manager in Python

Difficulty: Intermediate Est. Time: ~3 hours

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.

What You'll Build

A command-line password manager with:

  • Encrypted password storage
  • Master password protection
  • Password generator
  • Add, view, update, and delete passwords
  • Search functionality
What You'll Learn
  • 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
Why Encryption Matters

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.

Bash
# 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

File 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.

Python
# 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
Key Derivation Explained

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.

Python
# 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
Security Best Practice

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.

Python
# 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.

Python
# 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.

Python
# 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()
__init__.py Files

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

Bash
# Run the password manager
python -m password_manager.cli

# Or if you set up the package correctly
python password_manager/cli.py

Testing Steps

  1. Run the application
  2. Enter a master password (first time creates new vault)
  3. Use "generate" to create a password
  4. Use "add" to store credentials
  5. Use "list" to see all services
  6. Use "get" to retrieve a password
  7. Run again and try wrong password
Testing Checklist
  • ✓ 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
Important Security Note

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

Continue Learning

Explore more Python projects or check out our other tutorials!