"""
Password Manager CLI
A simple command-line password storage system with basic encryption.
Covers: Dictionaries, functions, loops, error handling, file I/O, input validation
Security Note: This uses basic XOR encryption for learning purposes only.
Real password managers use much stronger encryption (AES-256, etc.)
"""
import json
import os
import getpass
from datetime import datetime
# File where passwords are stored
VAULT_FILE = "password_vault.json"
def simple_encrypt(text, key):
"""
Simple XOR encryption for learning purposes.
In production, use proper encryption libraries like cryptography.
Args:
text: String to encrypt
key: Encryption key (string)
Returns:
Encrypted string in hexadecimal format
"""
# Convert key to bytes and repeat to match text length
key_bytes = (key * (len(text) // len(key) + 1))[:len(text)].encode()
text_bytes = text.encode()
# XOR each byte
encrypted = bytes(t ^ k for t, k in zip(text_bytes, key_bytes))
# Return as hexadecimal string
return encrypted.hex()
def simple_decrypt(encrypted_hex, key):
"""
Decrypt XOR-encrypted text.
Args:
encrypted_hex: Encrypted text in hexadecimal format
key: Decryption key (string)
Returns:
Decrypted string
"""
try:
# Convert hex string back to bytes
encrypted_bytes = bytes.fromhex(encrypted_hex)
# Convert key to bytes and repeat to match encrypted length
key_bytes = (key * (len(encrypted_bytes) // len(key) + 1))[:len(encrypted_bytes)].encode()
# XOR each byte
decrypted = bytes(e ^ k for e, k in zip(encrypted_bytes, key_bytes))
return decrypted.decode()
except Exception as e:
raise ValueError(f"Decryption failed: {e}")
def load_vault(master_password):
"""
Load the password vault from file.
Args:
master_password: Master password for decryption
Returns:
Dictionary containing all stored accounts
"""
if not os.path.exists(VAULT_FILE):
return {}
try:
# Read encrypted hex string
with open(VAULT_FILE, 'r') as f:
encrypted = f.read()
# Decrypt to get JSON string
decrypted_json = simple_decrypt(encrypted, master_password)
# Parse JSON - THIS WILL FAIL if password was wrong!
vault = json.loads(decrypted_json) # ← Garbage fails here
return vault
except json.JSONDecodeError:
print("⚠️ Wrong master password!")
return None
except Exception as e:
print(f"⚠️ Error: {e}")
return None
def save_vault(vault, master_password):
"""
Save the password vault to file with encryption.
Args:
vault: Dictionary containing all accounts
master_password: Master password for encryption
"""
try:
# Convert vault to JSON string
vault_json = json.dumps(vault)
# Encrypt the ENTIRE JSON string
encrypted = simple_encrypt(vault_json, master_password)
# Save encrypted string to file
with open(VAULT_FILE, 'w') as f:
f.write(encrypted) # Just the hex string
return True
except Exception as e:
print(f"✗ Error: {e}")
return False
def add_account(vault, master_password):
"""Add a new account to the vault."""
print("\n" + "=" * 50)
print("ADD NEW ACCOUNT")
print("=" * 50)
# Get account name (cannot be empty)
while True:
account_name = input("\nAccount name (e.g., 'Gmail', 'Facebook'): ").strip()
if account_name:
break
print("⚠️ Account name cannot be empty!")
# Check if account already exists
if account_name in vault:
overwrite = input(f"⚠️ '{account_name}' already exists. Overwrite? (yes/no): ").lower()
if overwrite != 'yes':
print("✗ Cancelled.")
return
# Get username
username = input("Username/Email: ").strip()
# Get password (hidden input)
while True:
password = getpass.getpass("Password: ")
if password:
password_confirm = getpass.getpass("Confirm password: ")
if password == password_confirm:
break
else:
print("⚠️ Passwords don't match! Try again.")
else:
print("⚠️ Password cannot be empty!")
# Optional fields
url = input("Website URL (optional): ").strip()
notes = input("Notes (optional): ").strip()
# Store account
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
vault[account_name] = {
'username': username,
'password': password,
'url': url,
'notes': notes,
'created': now,
'modified': now
}
save_vault(vault, master_password)
print(f"\n✓ Account '{account_name}' added successfully!")
def view_account(vault): # TODO phase 3
"""View details of a specific account."""
if not vault:
print("\n⚠️ Vault is empty! Add an account first.")
return
print("\n" + "=" * 50)
print("VIEW ACCOUNT")
print("=" * 50)
# Show available accounts
print("\nStored accounts:")
for i, account in enumerate(vault.keys(), 1):
print(f" {i}. {account}")
account_name = input("\nEnter account name: ").strip()
if account_name not in vault:
print(f"✗ Account '{account_name}' not found!")
return
# Display account details
account = vault[account_name]
print("\n" + "-" * 50)
print(f"Account: {account_name}")
print("-" * 50)
print(f"Username: {account['username']}")
print(f"Password: {account['password']}")
if account.get('url'):
print(f"URL: {account['url']}")
if account.get('notes'):
print(f"Notes: {account['notes']}")
print(f"Created: {account.get('created', 'Unknown')}")
print(f"Modified: {account.get('modified', 'Unknown')}")
print("-" * 50)
def list_accounts(vault): # TODO pahse 3
"""List all stored accounts."""
if not vault:
print("\n⚠️ Vault is empty! No accounts stored.")
return
print("\n" + "=" * 50)
print(f"STORED ACCOUNTS ({len(vault)} total)")
print("=" * 50)
for account_name, data in vault.items():
print(f"\n📧 {account_name}")
print(f" Username: {data['username']}")
if data.get('url'):
print(f" URL: {data['url']}")
def delete_account(vault, master_password):
"""Delete an account from the vault."""
if not vault:
print("\n⚠️ Vault is empty! Nothing to delete.")
return
print("\n" + "=" * 50)
print("DELETE ACCOUNT")
print("=" * 50)
# Show available accounts
print("\nStored accounts:")
for i, account in enumerate(vault.keys(), 1):
print(f" {i}. {account}")
account_name = input("\nEnter account name to delete: ").strip()
if account_name not in vault:
print(f"✗ Account '{account_name}' not found!")
return
# Confirm deletion
confirm = input(f"⚠️ Are you sure you want to delete '{account_name}'? (yes/no): ").lower()
if confirm == 'yes':
del vault[account_name]
save_vault(vault, master_password)
print(f"✓ Account '{account_name}' deleted successfully!")
else:
print("✗ Cancelled.")
def search_accounts(vault):
"""Search for accounts by name."""
if not vault:
print("\n⚠️ Vault is empty! Nothing to search.")
return
query = input("\nSearch for account: ").strip().lower()
results = [account for account in vault.keys() if query in account.lower()]
if results:
print(f"\n✓ Found {len(results)} result(s):")
for account in results:
print(f" • {account}")
else:
print(f"✗ No accounts found matching '{query}'")
def show_help():
"""Display available commands."""
print("\n" + "=" * 50)
print("AVAILABLE COMMANDS")
print("=" * 50)
print(" add - Add a new account")
print(" view - View account details")
print(" list - List all accounts")
print(" search - Search for accounts")
print(" delete - Delete an account")
print(" help - Show this help")
print(" quit - Exit password manager")
print("=" * 50)
def main():
"""Main program loop."""
print("\n" + "=" * 50)
print("🔐 PASSWORD MANAGER CLI 🔐")
print("=" * 50)
print("\n⚠️ SECURITY NOTE: This is for learning purposes only!")
print("Use a real password manager (1Password, Bitwarden, etc.)")
print("for actual sensitive data.\n")
# Get master password
master_password = getpass.getpass("Enter master password: ")
if not master_password:
print("✗ Master password cannot be empty!")
return
# Load vault
print("\n📂 Loading vault...")
vault = load_vault(master_password)
if vault is None:
print("✗ Failed to unlock vault. Exiting.")
return
if not vault:
print("✓ New vault created!")
else:
print(f"✓ Vault unlocked! ({len(vault)} accounts loaded)")
show_help()
# Main command loop
while True:
try:
command = input("\n🔐 > ").strip().lower()
if not command:
continue
if command == "quit" or command == "exit":
print("\n👋 Locking vault and exiting. Stay secure!")
break
elif command == "help":
show_help()
elif command == "add":
add_account(vault, master_password)
elif command == "view":
view_account(vault)
elif command == "list":
list_accounts(vault)
elif command == "search":
search_accounts(vault)
elif command == "delete":
delete_account(vault, master_password)
else:
print(f"✗ Unknown command: '{command}'. Type 'help' for available commands.")
except KeyboardInterrupt:
print("\n\n👋 Interrupted. Locking vault and exiting.")
break
except Exception as e:
print(f"⚠️ Error: {e}")
if __name__ == "__main__":
main()