Skip to content

Commit f50f612

Browse files
committed
Feat: Add GUI version using Tkinter for password management
1 parent c1f0eec commit f50f612

File tree

1 file changed

+258
-0
lines changed

1 file changed

+258
-0
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
# Import necessary libraries
2+
import os, random, datetime
3+
from tkinter import *
4+
from tkinter import messagebox
5+
from cryptography.fernet import Fernet
6+
7+
# ------------------------
8+
# FUNCTION DEFINITIONS
9+
# ------------------------
10+
11+
# Generate password based on user options
12+
def generate_password(length, use_lower, use_upper, use_digits, use_specials):
13+
lowercase = 'abcdefghijklmnopqrstuvwxyz'
14+
uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
15+
digits = '0123456789'
16+
specials = '!@#$%^&*()'
17+
18+
character_set = ''
19+
if use_lower: character_set += lowercase
20+
if use_upper: character_set += uppercase
21+
if use_digits: character_set += digits
22+
if use_specials: character_set += specials
23+
24+
if not character_set:
25+
return "Error: No character sets selected. Cannot generate password."
26+
27+
password = ''.join(random.choice(character_set) for _ in range(length))
28+
return password
29+
30+
# Check password strength
31+
def check_strength(length, use_lower, use_upper, use_digits, use_specials):
32+
score = use_lower + use_upper + use_digits + use_specials
33+
if length >= 12:
34+
score += 1
35+
36+
if score <= 2:
37+
return "Weak"
38+
elif score in (3, 4):
39+
return "Medium"
40+
else:
41+
return "Strong"
42+
43+
# Load or create encryption key
44+
def load_key():
45+
key_path = "secret.key"
46+
47+
if not os.path.exists(key_path):
48+
key = Fernet.generate_key()
49+
with open(key_path, "wb") as key_file:
50+
key_file.write(key)
51+
print("🔐 Key generated.")
52+
return key
53+
else:
54+
with open(key_path, "rb") as key_file:
55+
key = key_file.read()
56+
57+
# ✅ Verify it's a valid Fernet key
58+
try:
59+
Fernet(key) # This will raise ValueError if invalid
60+
print("🔑 Valid key loaded.")
61+
return key
62+
except ValueError:
63+
print("❌ Invalid key detected. Regenerating...")
64+
key = Fernet.generate_key()
65+
with open(key_path, "wb") as key_file:
66+
key_file.write(key)
67+
print("🔐 New key generated.")
68+
return key
69+
70+
# Encrypt the password
71+
def encrypt_password(password, key):
72+
fernet = Fernet(key)
73+
return fernet.encrypt(password.encode()).decode()
74+
75+
# Button click logic
76+
def on_generate_click():
77+
length_input = password_length_entry.get().strip()
78+
label = purpose_entry.get().strip() or "Unnamed"
79+
lower = use_lower.get()
80+
upper = use_upper.get()
81+
digits = use_digits.get()
82+
specials = use_specials.get()
83+
84+
# Handle default or invalid input safely
85+
if length_input == "":
86+
length = 12
87+
elif length_input.isdigit():
88+
length = int(length_input)
89+
if length <= 0:
90+
messagebox.showerror("Invalid Input", "Password length must be a positive number.")
91+
return
92+
else:
93+
messagebox.showerror("Invalid Input", "Enter a valid number for length.")
94+
return
95+
96+
# Generate the password
97+
password = generate_password(length, lower, upper, digits, specials)
98+
if password.startswith("Error"):
99+
messagebox.showwarning("Oops", password)
100+
return
101+
102+
strength = check_strength(length, lower, upper, digits, specials)
103+
104+
# Show result popup
105+
messagebox.showinfo(
106+
"Password Generated",
107+
f"🔐 Label: {label}\n\nPassword: {password}\n\n💪 Strength: {strength}"
108+
)
109+
110+
# Save to file (encrypted)
111+
key = load_key()
112+
encrypted = encrypt_password(password, key)
113+
114+
with open("saved_passwords.txt", "a") as file:
115+
timestamp = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S")
116+
file.write(f"\n[{timestamp}]\n")
117+
file.write(f"Label: {label}\n")
118+
file.write(f"Encrypted Password: {encrypted}\n")
119+
file.write(f"Strength: {strength}\n")
120+
file.write("-" * 40 + "\n")
121+
122+
messagebox.showinfo("✅ Saved", "Password saved securely to file.")
123+
124+
def on_view_passwords_click():
125+
try:
126+
key = load_key()
127+
fernet = Fernet(key)
128+
129+
# Check if saved passwords file exists
130+
if not os.path.exists("saved_passwords.txt"):
131+
messagebox.showinfo("No Data", "No saved passwords found.")
132+
return
133+
134+
# Read the file
135+
with open("saved_passwords.txt", "r") as file:
136+
lines = file.readlines()
137+
138+
display_data = []
139+
current = {}
140+
141+
# Parse each line
142+
for line in lines:
143+
line = line.strip()
144+
if line.startswith('['): # timestamp line
145+
current["Time"] = line
146+
elif line.startswith("Label:"):
147+
current["Label"] = line.replace("Label: ", "")
148+
elif line.startswith("Encrypted Password:"):
149+
encrypted = line.replace("Encrypted Password: ", "")
150+
try:
151+
decrypted = fernet.decrypt(encrypted.encode()).decode()
152+
except Exception:
153+
decrypted = "❌ Could not decrypt"
154+
current["Password"] = decrypted
155+
elif line.startswith("Strength:"):
156+
current["Strength"] = line.replace("Strength: ", "")
157+
elif line.startswith("-"):
158+
# end of entry
159+
display_data.append(current)
160+
current = {}
161+
162+
# If nothing parsed
163+
if not display_data:
164+
messagebox.showinfo("Empty", "No valid entried found.")
165+
return
166+
167+
# --------------------
168+
# New GUI Window to Display Saved Passwords
169+
# --------------------
170+
top = Toplevel(window)
171+
top.title("Saved Passwords")
172+
top.geometry("800x500")
173+
top.config(bg="dark slate gray")
174+
175+
# Create scrollbar
176+
scrollbar = Scrollbar(top)
177+
scrollbar.pack(side=RIGHT, fill=Y)
178+
179+
# Create text area and bind it to scrollbar
180+
text = Text(top, wrap=WORD, yscrollcommand=scrollbar.set, font="Consolas 12", bg="black", fg="light green")
181+
text.pack(expand=True, fill=BOTH)
182+
183+
for entry in display_data:
184+
text.insert(END, f"{entry['Time']}\n")
185+
text.insert(END, f"🔖 Label: {entry['Label']}\n")
186+
text.insert(END, f"🔐 Password: {entry['Password']}\n")
187+
text.insert(END, f"💪 Strength: {entry['Strength']}\n")
188+
text.insert(END, "-" * 40 + "\n\n")
189+
190+
text.config(state=DISABLED)
191+
scrollbar.config(command=text.yview)
192+
193+
except Exception as e:
194+
messagebox.showerror("Error", f"Something went wrong:\n{str(e)}")
195+
196+
# ------------------------
197+
# GUI LAYOUT
198+
# ------------------------
199+
200+
# Create main window
201+
window = Tk()
202+
window.title("Password Book")
203+
icon = PhotoImage(file="icons8-password-book-24.png")
204+
window.iconphoto(False, icon)
205+
window.geometry('900x700')
206+
window.configure(bg='dark slate gray')
207+
window.resizable(False, False)
208+
209+
# Create main frame
210+
frame = Frame(bg='dark slate gray')
211+
212+
# Welcome text
213+
welcome_label = Label(frame, text="Welcome to your Password Manager!", bg='dark slate gray', fg="azure", font="fixedsys 30 bold")
214+
welcome_label.grid(row=0, column=0, columnspan=2, sticky="news", pady=40)
215+
216+
# Label entry
217+
purpose_label = Label(frame, text="Enter a label or purpose for this password:", bg='dark slate gray', fg="ivory2", font=("Arial", 16))
218+
purpose_label.grid(row=1, column=0)
219+
220+
purpose_entry = Entry(frame, font=("Arial", 16))
221+
purpose_entry.grid(row=1, column=1, pady=20)
222+
223+
# Length entry
224+
password_length_label = Label(frame, text="Enter desired password length:", bg='dark slate gray', fg="ivory2", font=("Arial", 16))
225+
password_length_label.grid(row=2, column=0)
226+
227+
password_length_entry = Entry(frame, font=("Arial", 16))
228+
password_length_entry.grid(row=2, column=1, pady=20)
229+
230+
# Optional placeholder label (recommended)
231+
default_note = Label(frame, text="*Default is 12 if left empty", bg='dark slate gray', fg="gray80", font=("Arial", 10))
232+
default_note.grid(row=3, column=1, sticky='w')
233+
234+
# Character set checkboxes
235+
use_lower = BooleanVar()
236+
use_upper = BooleanVar()
237+
use_digits = BooleanVar()
238+
use_specials = BooleanVar()
239+
240+
Checkbutton(frame, text="Include lowercase letters", variable=use_lower, width=20, bg="dark slate gray", font=('Helvetica', 12, 'bold'), fg="ivory2", selectcolor="blue").grid(row=4, column=0, columnspan=2, pady=(30, 20))
241+
Checkbutton(frame, text="Include Uppercase letters", variable=use_upper, width=20, bg="dark slate gray", font=('Helvetica', 12, 'bold'), fg="ivory2", selectcolor="blue").grid(row=5, column=0, columnspan=2, pady=(0, 20))
242+
Checkbutton(frame, text="Include digits", variable=use_digits, width=20, bg="dark slate gray", font=('Helvetica', 12, 'bold'), fg="ivory2", selectcolor="blue").grid(row=6, column=0, columnspan=2, pady=(0, 20))
243+
Checkbutton(frame, text="Include special characters", variable=use_specials, width=20, bg="dark slate gray", font=('Helvetica', 12, 'bold'), fg="ivory2", selectcolor="blue").grid(row=7, column=0, columnspan=2, pady=(0, 30))
244+
245+
# Generate button
246+
generate_btn = Button(frame, text="Generate Password", bg="slate gray", fg="ivory2", font="Arial 16 bold", command=on_generate_click)
247+
generate_btn.grid(row=8, column=0, columnspan=2, pady=30)
248+
249+
# View passwords button
250+
view_btn = Button(frame, text="View Saved Passwords", bg="slate gray", fg="ivory2", font="Arial 16 bold")
251+
view_btn.grid(row=9, column=0, columnspan=2)
252+
view_btn.config(command=on_view_passwords_click)
253+
254+
# Pack the frame
255+
frame.pack()
256+
257+
# Start the app
258+
window.mainloop()

0 commit comments

Comments
 (0)