Project: Password Manager with Encryption

A password manager demonstrates critical security concepts including encryption, hashing, secure storage, and authentication while creating essential productivity tool. This project implements secure password vault supporting master password protection using hashing algorithms, password encryption with Fernet symmetric encryption, password generation creating strong random passwords, secure storage saving encrypted credentials to file, password retrieval decrypting on demand, and organizational features grouping by service or category. Password managers enable using unique strong passwords for every service without memorization burden, protect credentials with encryption making stolen files useless without master password, and teach essential security practices applicable to any application handling sensitive data.
This comprehensive project explores cryptography fundamentals using cryptography library with Fernet providing symmetric encryption, key derivation from master password using PBKDF2, and secure random generation, master password security hashing with SHA-256 preventing plaintext storage, salting adding randomness preventing rainbow table attacks, and verification comparing hashed values, password encryption with encrypt() converting plaintext to ciphertext, decrypt() reversing process with correct key, and base64 encoding for storage, password generation creating random strings with configurable length, including uppercase, lowercase, digits, and special characters, and ensuring sufficient entropy, data persistence storing encrypted passwords in JSON file, saving encryption key securely, and handling file operations safely, user interface providing registration for first-time setup, login validating master password, and menu-driven operations, and security best practices never storing master password plaintext, using strong key derivation, implementing account lockout after failed attempts, and clearing sensitive data from memory. This project teaches cryptography basics understanding encryption vs hashing, symmetric vs asymmetric encryption, key derivation functions, secure random generation using secrets module, working with bytes and strings, file security setting appropriate permissions, avoiding plaintext secrets, and exception handling for security, and password security requirements minimum length and complexity, avoiding common passwords, and regular updates, providing foundation for secure application development while creating immediately useful tool for personal credential management.
Encryption and Security Core
The encryption core implements fundamental security functions including master password hashing, encryption key derivation, and password encryption/decryption. Using the cryptography library's Fernet provides authenticated encryption ensuring both confidentiality and integrity. The master password never gets stored, only its hash.
# Encryption and Security Core
import os
import json
import hashlib
import base64
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2
import secrets
import string
# Configuration
MASTER_PASSWORD_FILE = 'master.hash'
SALT_FILE = 'salt.key'
PASSWORDS_FILE = 'passwords.enc'
# === Master password hashing ===
def hash_master_password(password, salt=None):
"""
Hash master password with salt.
Args:
password (str): Master password
salt (bytes): Salt for hashing (generates if None)
Returns:
tuple: (hash, salt) as bytes
"""
if salt is None:
salt = os.urandom(32) # 256-bit salt
# Use SHA-256 with many iterations
password_hash = hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt,
100000 # 100,000 iterations
)
return password_hash, salt
def verify_master_password(password, stored_hash, salt):
"""
Verify master password against stored hash.
Args:
password (str): Password to verify
stored_hash (bytes): Stored password hash
salt (bytes): Salt used for hashing
Returns:
bool: True if password matches
"""
password_hash, _ = hash_master_password(password, salt)
return password_hash == stored_hash
# === Encryption key derivation ===
def derive_key_from_password(password, salt):
"""
Derive encryption key from master password.
Args:
password (str): Master password
salt (bytes): Salt for key derivation
Returns:
bytes: Encryption key for Fernet
"""
kdf = PBKDF2(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000
)
key = base64.urlsafe_b64encode(
kdf.derive(password.encode('utf-8'))
)
return key
def initialize_cipher(master_password, salt):
"""
Initialize Fernet cipher with master password.
Args:
master_password (str): Master password
salt (bytes): Salt for key derivation
Returns:
Fernet: Initialized cipher
"""
key = derive_key_from_password(master_password, salt)
return Fernet(key)
# === Encryption and decryption ===
def encrypt_password(cipher, password):
"""
Encrypt password.
Args:
cipher (Fernet): Initialized cipher
password (str): Password to encrypt
Returns:
str: Encrypted password (base64)
"""
encrypted = cipher.encrypt(password.encode('utf-8'))
return encrypted.decode('utf-8')
def decrypt_password(cipher, encrypted_password):
"""
Decrypt password.
Args:
cipher (Fernet): Initialized cipher
encrypted_password (str): Encrypted password
Returns:
str: Decrypted password
"""
try:
decrypted = cipher.decrypt(encrypted_password.encode('utf-8'))
return decrypted.decode('utf-8')
except Exception:
raise ValueError("Failed to decrypt password. Wrong master password?")
# === Password generation ===
def generate_password(length=16, use_uppercase=True, use_lowercase=True,
use_digits=True, use_special=True):
"""
Generate secure random password.
Args:
length (int): Password length
use_uppercase (bool): Include uppercase letters
use_lowercase (bool): Include lowercase letters
use_digits (bool): Include digits
use_special (bool): Include special characters
Returns:
str: Generated password
"""
if length < 8:
raise ValueError("Password must be at least 8 characters")
# Build character set
characters = ''
if use_uppercase:
characters += string.ascii_uppercase
if use_lowercase:
characters += string.ascii_lowercase
if use_digits:
characters += string.digits
if use_special:
characters += string.punctuation
if not characters:
raise ValueError("Must include at least one character type")
# Generate password using secrets (cryptographically strong)
password = ''.join(secrets.choice(characters) for _ in range(length))
return password
def assess_password_strength(password):
"""
Assess password strength.
Args:
password (str): Password to assess
Returns:
tuple: (strength_score, feedback)
"""
score = 0
feedback = []
# Length check
if len(password) >= 12:
score += 2
elif len(password) >= 8:
score += 1
else:
feedback.append("Password should be at least 8 characters")
# Character variety
if any(c.isupper() for c in password):
score += 1
else:
feedback.append("Add uppercase letters")
if any(c.islower() for c in password):
score += 1
else:
feedback.append("Add lowercase letters")
if any(c.isdigit() for c in password):
score += 1
else:
feedback.append("Add numbers")
if any(c in string.punctuation for c in password):
score += 1
else:
feedback.append("Add special characters")
# Determine strength level
if score >= 6:
strength = "Strong"
elif score >= 4:
strength = "Medium"
else:
strength = "Weak"
return strength, feedback
# === File operations ===
def save_salt(salt):
"""
Save salt to file.
Args:
salt (bytes): Salt to save
"""
with open(SALT_FILE, 'wb') as f:
f.write(salt)
def load_salt():
"""
Load salt from file.
Returns:
bytes: Loaded salt or None
"""
try:
with open(SALT_FILE, 'rb') as f:
return f.read()
except FileNotFoundError:
return None
def save_master_hash(password_hash):
"""
Save master password hash.
Args:
password_hash (bytes): Hash to save
"""
with open(MASTER_PASSWORD_FILE, 'wb') as f:
f.write(password_hash)
def load_master_hash():
"""
Load master password hash.
Returns:
bytes: Loaded hash or None
"""
try:
with open(MASTER_PASSWORD_FILE, 'rb') as f:
return f.read()
except FileNotFoundError:
return None
def save_passwords(passwords):
"""
Save encrypted passwords.
Args:
passwords (dict): Password dictionary
Returns:
bool: True if successful
"""
try:
with open(PASSWORDS_FILE, 'w') as f:
json.dump(passwords, f, indent=2)
return True
except Exception as e:
print(f"Error saving passwords: {e}")
return False
def load_passwords():
"""
Load encrypted passwords.
Returns:
dict: Password dictionary
"""
try:
with open(PASSWORDS_FILE, 'r') as f:
return json.load(f)
except FileNotFoundError:
return {}
except json.JSONDecodeError:
print("Warning: Password file corrupted")
return {}
# === Security utilities ===
def clear_sensitive_data(data):
"""
Securely clear sensitive data from memory.
Args:
data (str): Data to clear
Note: This is best-effort in Python
"""
# Overwrite memory (best effort in Python)
if isinstance(data, str):
data = '0' * len(data)
def is_first_time_setup():
"""
Check if this is first time setup.
Returns:
bool: True if no master password exists
"""
return not os.path.exists(MASTER_PASSWORD_FILE)
print("Encryption core implementation completed!")Password Management Operations
Password management operations implement CRUD functionality for stored credentials. Each password entry includes service name, username, encrypted password, optional notes, and timestamps. Functions handle adding new credentials, retrieving passwords with decryption, updating existing entries, and secure deletion.
# Password Management Operations
import uuid
from datetime import datetime
import getpass
# Import encryption functions
from encryption_core import (
encrypt_password, decrypt_password,
load_passwords, save_passwords
)
# === Password entry structure ===
"""
Password Entry:
{
'id': 'unique-uuid',
'service': 'Service name',
'username': 'Username/email',
'password': 'encrypted_password',
'url': 'Optional URL',
'notes': 'Optional notes',
'created_at': 'timestamp',
'updated_at': 'timestamp'
}
"""
# === CRUD operations ===
def add_password_entry(cipher, service, username, password,
url='', notes=''):
"""
Add new password entry.
Args:
cipher (Fernet): Encryption cipher
service (str): Service name
username (str): Username
password (str): Password to encrypt
url (str): Optional URL
notes (str): Optional notes
Returns:
bool: True if successful
"""
# Validate inputs
if not service or not username or not password:
print("\nโ Service, username, and password are required")
return False
# Load existing passwords
passwords = load_passwords()
# Check for duplicate
for entry in passwords.values():
if (entry['service'].lower() == service.lower() and
entry['username'].lower() == username.lower()):
print("\nโ Entry already exists for this service and username")
overwrite = input("Overwrite? (yes/no): ").strip().lower()
if overwrite != 'yes':
return False
# Remove old entry
passwords = {k: v for k, v in passwords.items()
if not (v['service'].lower() == service.lower() and
v['username'].lower() == username.lower())}
# Encrypt password
encrypted_password = encrypt_password(cipher, password)
# Create entry
entry_id = str(uuid.uuid4())
timestamp = datetime.now().isoformat()
entry = {
'id': entry_id,
'service': service.strip(),
'username': username.strip(),
'password': encrypted_password,
'url': url.strip(),
'notes': notes.strip(),
'created_at': timestamp,
'updated_at': timestamp
}
# Add to passwords
passwords[entry_id] = entry
# Save
if save_passwords(passwords):
print(f"\nโ Password saved successfully!")
return True
else:
print("\nโ Failed to save password")
return False
def get_password_entry(cipher, service, username=None):
"""
Retrieve and decrypt password.
Args:
cipher (Fernet): Decryption cipher
service (str): Service name
username (str): Optional username filter
Returns:
list: Matching entries with decrypted passwords
"""
passwords = load_passwords()
matches = []
for entry in passwords.values():
if entry['service'].lower() == service.lower():
if username is None or entry['username'].lower() == username.lower():
# Decrypt password
try:
decrypted = decrypt_password(cipher, entry['password'])
entry_copy = entry.copy()
entry_copy['password'] = decrypted
matches.append(entry_copy)
except ValueError as e:
print(f"\nโ Error decrypting password: {e}")
return matches
def update_password_entry(cipher, entry_id, password=None,
username=None, url=None, notes=None):
"""
Update existing password entry.
Args:
cipher (Fernet): Encryption cipher
entry_id (str): Entry ID to update
password (str): New password (optional)
username (str): New username (optional)
url (str): New URL (optional)
notes (str): New notes (optional)
Returns:
bool: True if successful
"""
passwords = load_passwords()
if entry_id not in passwords:
print(f"\nโ Entry with ID '{entry_id}' not found")
return False
entry = passwords[entry_id]
# Update fields
if password:
entry['password'] = encrypt_password(cipher, password)
if username:
entry['username'] = username.strip()
if url is not None:
entry['url'] = url.strip()
if notes is not None:
entry['notes'] = notes.strip()
entry['updated_at'] = datetime.now().isoformat()
# Save
if save_passwords(passwords):
print("\nโ Entry updated successfully!")
return True
else:
print("\nโ Failed to update entry")
return False
def delete_password_entry(entry_id):
"""
Delete password entry.
Args:
entry_id (str): Entry ID to delete
Returns:
bool: True if successful
"""
passwords = load_passwords()
if entry_id not in passwords:
print(f"\nโ Entry with ID '{entry_id}' not found")
return False
# Remove entry
del passwords[entry_id]
# Save
if save_passwords(passwords):
print("\nโ Entry deleted successfully!")
return True
else:
print("\nโ Failed to delete entry")
return False
def list_all_entries():
"""
List all password entries (without passwords).
Returns:
list: List of entries without passwords
"""
passwords = load_passwords()
entries = []
for entry in passwords.values():
# Create copy without password
safe_entry = {
'id': entry['id'],
'service': entry['service'],
'username': entry['username'],
'url': entry.get('url', ''),
'created_at': entry['created_at']
}
entries.append(safe_entry)
return sorted(entries, key=lambda x: x['service'].lower())
def search_entries(query):
"""
Search entries by service name.
Args:
query (str): Search query
Returns:
list: Matching entries
"""
passwords = load_passwords()
query_lower = query.lower()
matches = []
for entry in passwords.values():
if (query_lower in entry['service'].lower() or
query_lower in entry['username'].lower() or
query_lower in entry.get('notes', '').lower()):
safe_entry = {
'id': entry['id'],
'service': entry['service'],
'username': entry['username'],
'url': entry.get('url', '')
}
matches.append(safe_entry)
return matches
# === Display functions ===
def display_entry(entry, show_password=False):
"""
Display password entry.
Args:
entry (dict): Entry to display
show_password (bool): Whether to show password
"""
print(f"\nService: {entry['service']}")
print(f"Username: {entry['username']}")
if show_password and 'password' in entry:
print(f"Password: {entry['password']}")
if entry.get('url'):
print(f"URL: {entry['url']}")
if entry.get('notes'):
print(f"Notes: {entry['notes']}")
print(f"ID: {entry['id']}")
print(f"Created: {entry.get('created_at', 'N/A')}")
def display_entry_list(entries):
"""
Display list of entries.
Args:
entries (list): List of entries
"""
if not entries:
print("\nNo entries found.")
return
print("\n" + "="*60)
print(" STORED PASSWORDS")
print("="*60)
for i, entry in enumerate(entries, 1):
print(f"\n{i}. {entry['service']}")
print(f" Username: {entry['username']}")
print(f" ID: {entry['id']}")
print()
print("Password management operations completed!")getpass module and use getpass.getpass() for password input. Prevents passwords appearing on screen.Security Best Practices
- Use strong encryption: Fernet provides authenticated encryption (AES-128 + HMAC). Never implement custom encryption. Use proven libraries
- Never store master password: Only store hash using PBKDF2 with high iteration count (100,000+). Use unique salt per installation
- Derive keys properly: Use key derivation functions (PBKDF2, Argon2) converting passwords to encryption keys. Add computational cost
- Implement login attempts limit: Lock out after 3-5 failed attempts. Prevents brute force attacks. Consider adding delays
- Use getpass for input: Import getpass module preventing passwords appearing on screen. Essential for terminal security
- Generate strong passwords: Use secrets module (cryptographically secure). Minimum 12 characters with mixed character types
- Secure file permissions: Set restrictive permissions on password file (chmod 600 on Unix). Prevent unauthorized access
- Handle exceptions carefully: Don't expose encryption details in error messages. Log security events for monitoring
- Clear sensitive data: Overwrite passwords in memory when done (best effort in Python). Minimize exposure time
- Consider backup encryption: Encrypt backup files. Never store unencrypted copies. Implement secure backup procedures
Conclusion
Building password manager demonstrates essential security concepts through practical implementation. The project includes encryption fundamentals using cryptography library with Fernet providing authenticated symmetric encryption combining AES-128 and HMAC, key derivation with PBKDF2 converting master password to encryption key using salt and high iteration count, and secure random generation with secrets module for passwords and salts, master password security implementing hash_master_password() using PBKDF2-HMAC-SHA256 with 100,000 iterations, storing only hash never plaintext, verify_master_password() comparing hashes, and salt storage preventing rainbow table attacks, password operations with add_password_entry() encrypting before storage, get_password_entry() retrieving and decrypting, update_password_entry() modifying encrypted data, delete_password_entry() securely removing, and list operations showing metadata without passwords, and authentication system implementing first-time setup creating master password, subsequent login with failed attempt tracking, cipher initialization from master password, and session management.
Key learning outcomes include cryptography basics understanding encryption protecting confidentiality, hashing providing one-way transformation, key derivation converting passwords to keys, authenticated encryption preventing tampering, working with bytes and strings converting between encodings, base64 encoding for storage, handling binary data, security practices never storing plaintext passwords, using proven libraries not custom crypto, implementing proper key derivation, limiting login attempts, file operations saving encrypted data to JSON, loading with error handling, setting file permissions, backing up securely, user interface design using getpass hiding password input, providing clear feedback, handling exceptions gracefully, and password generation creating strong random passwords with configurable complexity, assessing strength, enforcing minimums. Security considerations emphasize using Fernet not custom encryption, hashing master password with PBKDF2, deriving keys properly adding computational cost, limiting login attempts preventing brute force, using getpass for sensitive input, generating with secrets module, setting restrictive file permissions, handling exceptions carefully not exposing details, clearing sensitive data from memory, and encrypting backups. Possible enhancements include password strength meter showing visual indicator, breach checking using haveibeenpwned API, two-factor authentication adding TOTP support, auto-lock implementing timeout, cloud sync encrypting before upload, browser integration creating extensions, mobile companion app, import/export supporting CSV/JSON, categories organizing passwords, and audit log tracking access. This project provides foundation for understanding applied cryptography, building security-conscious applications, implementing authentication systems, and handling sensitive data properly while creating useful tool demonstrating that security and usability can coexist when implemented thoughtfully with proven libraries and best practices.
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


