Skip to content

Commit 6affaa9

Browse files
Merge pull request #1762 from knoxiboy/1671-feat-password-manager-sqlite
feat: Build an Advanced Password Manager with SQLite Database (#1671)
2 parents 031bda8 + 2dacfaa commit 6affaa9

8 files changed

Lines changed: 465 additions & 0 deletions

File tree

projects_registry.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,20 @@
691691
"path": "games/Pygame-Chess/main.py"
692692
},
693693
{
694+
"name": "Advanced Password Manager",
695+
"emoji": "🔐",
696+
"category": "security",
697+
"difficulty": "beginner",
698+
"description": "Secure password vault with SQLite database and stream encryption.",
699+
"keywords": [
700+
"password",
701+
"manager",
702+
"sqlite",
703+
"security",
704+
"encryption",
705+
"vault"
706+
],
707+
"path": "security/password_manager/main.py"
694708
"name": "High-Performance Multithreaded Web Scraper",
695709
"emoji": "🕷️",
696710
"category": "utilities",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Advanced Password Manager with SQLite Database
2+
3+
A zero-external-dependency password manager with SQLite persistence, PBKDF2 key derivation, dynamic nonce stream encryption, and password strength analysis.
4+
5+
## Features
6+
7+
- **Master Authentication**: Hashes and salts master password via PBKDF2 (100,000 iterations).
8+
- **SQLite Storage**: Encrypted credential storage in persistent SQLite database (`vault.db`).
9+
- **Zero Dependencies**: Pure Python standard library implementation (`sqlite3`, `hashlib`, `secrets`, `getpass`).
10+
- **Strength Analysis**: Evaluates password complexity and strength level.
11+
12+
## Usage
13+
14+
```bash
15+
python security/password_manager/main.py
16+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
__init__.py for security/password_manager module.
3+
"""
4+
5+
from security.password_manager.vault import PasswordVault
6+
from security.password_manager.db import DatabaseManager
7+
from security.password_manager.crypto import (
8+
generate_salt, derive_key, hash_master_password,
9+
encrypt_password, decrypt_password, evaluate_password_strength
10+
)
11+
12+
__all__ = [
13+
"PasswordVault", "DatabaseManager",
14+
"generate_salt", "derive_key", "hash_master_password",
15+
"encrypt_password", "decrypt_password", "evaluate_password_strength"
16+
]
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Cryptographic utility module using PBKDF2 key derivation and stream cipher encryption.
3+
Zero external dependencies, built using standard library hashlib & secrets.
4+
"""
5+
6+
import hashlib
7+
import secrets
8+
from typing import Tuple
9+
10+
11+
def generate_salt(length: int = 16) -> bytes:
12+
"""Generate cryptographically secure random salt."""
13+
return secrets.token_bytes(length)
14+
15+
16+
def derive_key(master_password: str, salt: bytes, iterations: int = 100_000) -> bytes:
17+
"""Derive 256-bit encryption key from master password using PBKDF2 HMAC SHA-256."""
18+
return hashlib.pbkdf2_hmac('sha256', master_password.encode('utf-8'), salt, iterations, dklen=32)
19+
20+
21+
def hash_master_password(master_password: str, salt: bytes) -> bytes:
22+
"""Hash master password for storage & verification."""
23+
return derive_key(master_password, salt, iterations=100_000)
24+
25+
26+
def encrypt_password(plaintext: str, key: bytes) -> Tuple[bytes, bytes]:
27+
"""
28+
Encrypt plaintext password using derived key and unique nonce via SHA-256 keystream.
29+
Returns (nonce, ciphertext).
30+
"""
31+
nonce = secrets.token_bytes(16)
32+
plaintext_bytes = plaintext.encode('utf-8')
33+
keystream = bytearray()
34+
35+
# Generate keystream block by block
36+
block_num = 0
37+
while len(keystream) < len(plaintext_bytes):
38+
block = hashlib.sha256(key + nonce + block_num.to_bytes(4, 'big')).digest()
39+
keystream.extend(block)
40+
block_num += 1
41+
42+
ciphertext = bytes(p ^ k for p, k in zip(plaintext_bytes, keystream[:len(plaintext_bytes)]))
43+
return nonce, ciphertext
44+
45+
46+
def decrypt_password(nonce: bytes, ciphertext: bytes, key: bytes) -> str:
47+
"""Decrypt ciphertext back to plaintext string."""
48+
keystream = bytearray()
49+
block_num = 0
50+
51+
while len(keystream) < len(ciphertext):
52+
block = hashlib.sha256(key + nonce + block_num.to_bytes(4, 'big')).digest()
53+
keystream.extend(block)
54+
block_num += 1
55+
56+
plaintext_bytes = bytes(c ^ k for c, k in zip(ciphertext, keystream[:len(ciphertext)]))
57+
return plaintext_bytes.decode('utf-8')
58+
59+
60+
def evaluate_password_strength(password: str) -> Tuple[str, int]:
61+
"""
62+
Analyze password strength on scale 0-100 and return strength level string.
63+
"""
64+
score = 0
65+
if len(password) >= 8:
66+
score += 20
67+
if len(password) >= 12:
68+
score += 20
69+
if any(c.isupper() for c in password):
70+
score += 15
71+
if any(c.islower() for c in password):
72+
score += 15
73+
if any(c.isdigit() for c in password):
74+
score += 15
75+
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
76+
score += 15
77+
78+
if score < 40:
79+
return "Weak 🔴", score
80+
elif score < 75:
81+
return "Medium 🟡", score
82+
else:
83+
return "Strong 🟢", score

security/password_manager/db.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""
2+
Database initialization and connection management for SQLite Password Manager.
3+
"""
4+
5+
import sqlite3
6+
import os
7+
from typing import Optional
8+
9+
10+
class DatabaseManager:
11+
def __init__(self, db_path: str = "security/password_manager/vault.db"):
12+
self.db_path = db_path
13+
os.makedirs(os.path.dirname(os.path.abspath(self.db_path)), exist_ok=True)
14+
self.init_db()
15+
16+
def get_connection(self) -> sqlite3.Connection:
17+
conn = sqlite3.connect(self.db_path)
18+
conn.row_factory = sqlite3.Row
19+
return conn
20+
21+
def init_db(self):
22+
"""Create master authentication and password storage tables."""
23+
with self.get_connection() as conn:
24+
cursor = conn.cursor()
25+
26+
# Master auth table for master password hash & salt
27+
cursor.execute("""
28+
CREATE TABLE IF NOT EXISTS master_auth (
29+
id INTEGER PRIMARY KEY CHECK (id = 1),
30+
salt BLOB NOT NULL,
31+
password_hash BLOB NOT NULL,
32+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
33+
);
34+
""")
35+
36+
# Encrypted vault passwords table
37+
cursor.execute("""
38+
CREATE TABLE IF NOT EXISTS passwords (
39+
id INTEGER PRIMARY KEY AUTOINCREMENT,
40+
service_name TEXT NOT NULL,
41+
username TEXT NOT NULL,
42+
nonce BLOB NOT NULL,
43+
encrypted_password BLOB NOT NULL,
44+
category TEXT DEFAULT 'General',
45+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
46+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
47+
);
48+
""")
49+
conn.commit()
50+
cursor.close()

security/password_manager/main.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
Interactive Terminal UI for Password Manager with SQLite Database.
3+
"""
4+
5+
import sys
6+
import getpass
7+
from security.password_manager import PasswordVault
8+
9+
10+
def main():
11+
print("=" * 60)
12+
print(" 🔐 Advanced Password Manager (SQLite Vault)")
13+
print("=" * 60)
14+
15+
vault = PasswordVault()
16+
17+
if not vault.is_initialized():
18+
print("\n✨ Welcome! Setting up master vault password.")
19+
pwd = getpass.getpass("🔑 Create Master Password: ").strip()
20+
pwd_confirm = getpass.getpass("🔑 Confirm Master Password: ").strip()
21+
22+
if pwd != pwd_confirm:
23+
print("❌ Passwords do not match. Exiting.")
24+
sys.exit(1)
25+
26+
if not pwd:
27+
print("❌ Master password cannot be empty.")
28+
sys.exit(1)
29+
30+
vault.setup_master_password(pwd)
31+
print("✅ Master password created and vault initialized successfully!\n")
32+
else:
33+
pwd = getpass.getpass("🔑 Enter Master Password to Unlock Vault: ").strip()
34+
if not vault.unlock(pwd):
35+
print("❌ Incorrect Master Password. Vault remains locked.")
36+
sys.exit(1)
37+
print("✅ Vault unlocked successfully!\n")
38+
39+
while True:
40+
print("\n" + "-" * 40)
41+
print("1 🔑 View / Search Passwords")
42+
print("2 ➕ Add New Password")
43+
print("3 ❌ Delete Password Entry")
44+
print("4 🚪 Lock & Exit")
45+
print("-" * 40)
46+
47+
choice = input("🎯 Choose option (1-4): ").strip()
48+
49+
if choice == "1":
50+
query = input("🔍 Enter search term (or press Enter to view all): ").strip()
51+
records = vault.search_passwords(query) if query else vault.get_all_passwords()
52+
53+
if not records:
54+
print("📭 No matching password entries found.")
55+
else:
56+
print(f"\n📋 Password Records ({len(records)} found):")
57+
print(f"{'ID':<4} | {'Service':<20} | {'Username':<20} | {'Password':<20} | {'Strength':<10}")
58+
print("-" * 85)
59+
for r in records:
60+
print(f"{r['id']:<4} | {r['service_name']:<20} | {r['username']:<20} | {r['password']:<20} | {r['strength']:<10}")
61+
62+
elif choice == "2":
63+
service = input("🌐 Service Name (e.g. GitHub): ").strip()
64+
username = input("👤 Username/Email: ").strip()
65+
password = getpass.getpass("🔒 Password: ").strip()
66+
category = input("🏷️ Category (default: General): ").strip() or "General"
67+
68+
if not service or not username or not password:
69+
print("⚠️ Service, username, and password fields are required.")
70+
continue
71+
72+
entry_id = vault.add_password(service, username, password, category)
73+
print(f"✅ Saved password entry (ID: {entry_id})!")
74+
75+
elif choice == "3":
76+
try:
77+
entry_id = int(input("🗑️ Enter Entry ID to delete: ").strip())
78+
if vault.delete_password(entry_id):
79+
print(f"✅ Deleted entry ID {entry_id} successfully.")
80+
else:
81+
print(f"❌ Entry ID {entry_id} not found.")
82+
except ValueError:
83+
print("⚠️ Invalid ID format.")
84+
85+
elif choice == "4":
86+
print("\n👋 Vault locked. Goodbye!\n")
87+
break
88+
else:
89+
print("⚠️ Invalid option.")
90+
91+
92+
if __name__ == "__main__":
93+
main()

0 commit comments

Comments
 (0)