|
| 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