-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvault.py
More file actions
161 lines (125 loc) · 5.31 KB
/
vault.py
File metadata and controls
161 lines (125 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
"""
vault.py — CLI Password Vault
A terminal-based password manager that stores encrypted entries locally.
"""
import os
import pickle
import hashlib
import secrets
import string
import getpass
import pyperclip
VAULT_FILE = "vault.pkl"
# ── Hashing ──────────────────────────────────────────────────────────────────
def hash_password(password: str) -> str:
"""Return the SHA-256 hex digest of *password*."""
return hashlib.sha256(password.encode()).hexdigest()
def verify_password(stored_hash: str, attempt: str) -> bool:
"""Return True if *attempt* matches *stored_hash*."""
return stored_hash == hash_password(attempt)
# ── Persistence ───────────────────────────────────────────────────────────────
def save_vault(data: dict) -> None:
"""Persist *data* to disk."""
with open(VAULT_FILE, "wb") as fh:
pickle.dump(data, fh)
def load_vault() -> dict:
"""Load vault from disk, or return an empty vault if none exists."""
if os.path.exists(VAULT_FILE):
with open(VAULT_FILE, "rb") as fh:
return pickle.load(fh)
return {"master_hash": None, "entries": {}}
# ── CRUD operations ───────────────────────────────────────────────────────────
def add_entry(vault: dict) -> None:
"""Prompt the user for site credentials and save them to *vault*."""
site = input("Site: ").strip()
username = input("Username: ").strip()
password = input("Password (leave blank to generate one): ").strip()
if not password:
password = generate_password()
print(f"Generated password: {password}")
notes = input("Notes: ").strip()
vault["entries"][site] = {
"username": username,
"password": password,
"notes": notes,
}
print(f"Entry for '{site}' saved.")
def delete_entry(vault: dict) -> None:
"""Remove a site entry from *vault* after user confirmation."""
site = input("Site to delete: ").strip()
if site in vault["entries"]:
confirm = input(f"Delete '{site}'? (y/n): ").strip().lower()
if confirm == "y":
del vault["entries"][site]
print(f"'{site}' deleted.")
else:
print("Site not found.")
def search_entry(vault: dict) -> None:
"""Search entries by site name and optionally copy the password."""
query = input("Search site: ").strip().lower()
matches = [s for s in vault["entries"] if query in s.lower()]
if not matches:
print("No matching entries found.")
return
for site in matches:
entry = vault["entries"][site]
print(f"\n Site : {site}")
print(f" Username : {entry['username']}")
print(f" Notes : {entry['notes']}")
copy = input(" Copy password to clipboard? (y/n): ").strip().lower()
if copy == "y":
pyperclip.copy(entry["password"])
print(" Password copied to clipboard.")
def list_entries(vault: dict) -> None:
"""Print all stored site names."""
if not vault["entries"]:
print("No entries yet.")
return
print("\nStored sites:")
for site in vault["entries"]:
print(f" - {site}")
# ── Password generator ────────────────────────────────────────────────────────
def generate_password() -> str:
"""Generate a cryptographically secure random password."""
try:
length = int(input("Length (default 16): ").strip() or 16)
except ValueError:
length = 16
chars = string.ascii_letters + string.digits + string.punctuation
return "".join(secrets.choice(chars) for _ in range(length))
# ── Entry point ───────────────────────────────────────────────────────────────
def main() -> None:
vault = load_vault()
# First-time setup
if not vault["master_hash"]:
pw = getpass.getpass("Create a master password: ")
vault["master_hash"] = hash_password(pw)
save_vault(vault)
print("Vault created. Remember your master password — it cannot be recovered.\n")
# Authenticate
attempt = getpass.getpass("Master password: ")
if not verify_password(vault["master_hash"], attempt):
print("Incorrect password.")
return
print("Unlocked.\n")
menu = "\n[1] Add [2] Search [3] List [4] Delete [5] Quit"
while True:
print(menu)
choice = input("> ").strip()
if choice == "1":
add_entry(vault)
save_vault(vault)
elif choice == "2":
search_entry(vault)
elif choice == "3":
list_entries(vault)
elif choice == "4":
delete_entry(vault)
save_vault(vault)
elif choice == "5":
print("Goodbye.")
break
else:
print("Invalid choice.")
if __name__ == "__main__":
main()