diff --git a/Secure_Password_Manager/.gitignore b/Secure_Password_Manager/.gitignore new file mode 100644 index 0000000..17334ee --- /dev/null +++ b/Secure_Password_Manager/.gitignore @@ -0,0 +1,2 @@ +saved_passwords.txt +secret.key \ No newline at end of file diff --git a/Secure_Password_Manager/README b/Secure_Password_Manager/README new file mode 100644 index 0000000..6ac955f --- /dev/null +++ b/Secure_Password_Manager/README @@ -0,0 +1,114 @@ +# πŸ” Secure Password Manager (CLI + GUI) + +This is a simple and secure Password Manager built with Python. It includes both a **Tkinter-based GUI (Graphical User Interface)** and a **CLI (Command Line Interface)** version. + +--- + +## πŸ“Œ Features + +* βœ… Generate strong, random passwords +* βœ… Choose what characters to include (lowercase, uppercase, digits, special characters) +* βœ… Real-time password strength feedback +* βœ… Save passwords with labels (e.g., Gmail, Netflix) +* βœ… Encode/Encrypt passwords for security +* βœ… View saved passwords with automatic decryption +* βœ… GUI built using Python's `tkinter` library +* βœ… CLI version with command-line options using `argparse` + +--- + +## πŸ’‘ How It Works + +### Tkinter GUI + +The graphical interface is built using `tkinter`, Python’s standard GUI toolkit. It provides a clean interface for generating, encrypting, and viewing passwords. + +### Generate Password + +1. Choose the password length. +2. Select character types using checkboxes. +3. Hit the **Generate Password** button. +4. The password is displayed with its strength (Weak, Medium, or Strong). +5. The password is then **encrypted** and saved to a file. + +### View Saved Passwords + +1. Click the **View Saved Passwords** button in the GUI. +2. If the encryption key exists, the program decrypts and displays all saved passwords in a pop-up window. +3. If the key is missing or corrupted, an error message is shown. + +--- + +## πŸ› οΈ Technologies Used + +| Component | Tool/Library | +|----------|--------------| +| Programming Language | Python | +| GUI Library | Tkinter | +| Encryption | cryptography (Fernet) | +| Encoding (CLI version) | base64 | +| CLI Parser | argparse | +| Version Control | Git | + +--- + +## πŸ—ƒοΈ Project Structure + +```bash +Password_Manager/ +β”œβ”€β”€ password_manager_gui.py # Tkinter-based GUI application +β”œβ”€β”€ password_manager_cli.py # CLI-based password generator +β”œβ”€β”€ view_passwords_cli.py # View decoded Base64 passwords (CLI) +β”œβ”€β”€ decrypt_passwords_cli.py # Decrypt encrypted passwords (CLI) +β”œβ”€β”€ saved_passwords.txt # File where encrypted passwords are saved +β”œβ”€β”€ secret.key # File containing Fernet encryption key +β”œβ”€β”€ README.md +└── .gitignore +``` + +--- + +## ⚠️ Security Note + +This project uses **Fernet encryption** to protect your saved passwords. + +Files like `secret.key` and `saved_passwords.txt` are excluded from version control using `.gitignore`. + +> Do not share your `secret.key` with anyone. If you lose it, saved passwords cannot be decrypted. + +--- + +## 🧠 Ideal For + +- Python beginners learning `tkinter` +- Students building secure Python projects +- Learning encryption basics with `cryptography` +- Practicing Git and GitHub workflow with versioned features + +--- + +## βš™οΈ Setup Instructions + +1. Clone the repo + `git clone https://github.com/Your-Username/Secure_Password_Manager.git` + +2. Install dependencies + `pip install cryptography` + +3. Run GUI + `python password_manager_gui.py` + +4. Or use CLI + `python password_manager_cli.py --length 12 --label Gmail --lower --upper --digits --specials` + +--- + +## ⭐ GitHub Ready + +This project was built using proper Git practices: + +* Every feature added on a new branch +* Meaningful commit messages +* Clean merge history + +--- diff --git a/Secure_Password_Manager/cli/decrypt_passwords_cli.py b/Secure_Password_Manager/cli/decrypt_passwords_cli.py new file mode 100644 index 0000000..8792c7e --- /dev/null +++ b/Secure_Password_Manager/cli/decrypt_passwords_cli.py @@ -0,0 +1,63 @@ +from cryptography import fernet +import os + +KEY_FILE = "secret.key" +PASSWORD_FILE = "saved_passwords.txt" + +# STEP 1: Load the encryption key +def load_key(): + if not os.path.exists(KEY_FILE): + print("❌ Encryption key not found. Cannot decrypt passwords.") + return None + with open(KEY_FILE, "rb") as f: + return f.read() + +# STEP 2: Decrypt the passwords +def decrypt_password(encrypted_text, fernet_cipher_suite): # Renamed parameter for clarity + try: + return fernet_cipher_suite.decrypt(encrypted_text.encode()).decode() + except Exception: + return "[Decryption Failed]" + +# STEP 3: Read and decrypt all entries +def view_passwords(): + if not os.path.exists(PASSWORD_FILE): + print("❌ No saved passwords found") + return + + key = load_key() + if not key: + return + + # Fernet = fernet(key) # Original line causing TypeError + active_fernet_cipher = fernet.Fernet(key) # Correctly instantiate Fernet class from the module + + print("πŸ” Saved Encrypted Passwords\n" + "=" * 40) + with open(PASSWORD_FILE, "r") as file: + lines = file.readlines() + + current_block = {} + + for line in lines: + line = line.strip() + + if line.startswith("[") and "]" in line: + current_block["timestamp"] = line.strip("[]") + elif line.startswith("Label:"): + current_block["label"] = line.split("Label:")[1].strip() + elif line.startswith("Encrypted Password:"): + current_block["encrypted"] = line.split("Encrypted Password:")[1].strip() + elif line.startswith("Included -"): + current_block["options"] = line.split("Included -")[1].strip() + elif line.startswith("-" * 10): + # print everything together + print(f"\nπŸ“… Date/Time: {current_block.get('timestamp', '[Unknown]')}") + print(f"🏷️ Label: {current_block.get('label', '[None]')}") + print(f"πŸ”“ Password: {decrypt_password(current_block.get('encrypted', ''), active_fernet_cipher)}") # Pass the Fernet instance + print(f"βš™οΈ Options: {current_block.get('options', '[Not specified]')}") + print("-" * 40) + current_block = {} # Reset for next block + +# STEP 4: Entry point +if __name__ == '__main__': + view_passwords() \ No newline at end of file diff --git a/Secure_Password_Manager/cli/password_manager_cli.py b/Secure_Password_Manager/cli/password_manager_cli.py new file mode 100644 index 0000000..e03070f --- /dev/null +++ b/Secure_Password_Manager/cli/password_manager_cli.py @@ -0,0 +1,114 @@ +import random, datetime, base64, argparse, os +from cryptography.fernet import Fernet + +# πŸ”‘ File where secret key will be stored +KEY_FILE = "secret.key" + +# ------------------------------------------------------ +# STEP 1: Generate a new encryption key (only once)) +def generate_key(): + key = Fernet.generate_key() + with open(KEY_FILE, "wb") as f: + f.write(key) + +# STEP 2: Load the ecryption key +def load_key(): + if not os.path.exists(KEY_FILE): + print("No Key found. Creating a new one...") + generate_key() + with open(KEY_FILE, "rb") as f: + return f.read() + +# STEP 3: Generate password based on user settings +def generate_password(length, use_lower, use_upper, use_digits, use_specials): + lowercase = 'abcdefghijklmnopqrstuvwxyz' + uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + digits = '0123456789' + specials = '!@#$%^&*()' + + # Build character set based on user input + character_set = '' + + if use_lower: + character_set = character_set + lowercase + if use_upper: + character_set = character_set + uppercase + if use_digits: + character_set = character_set + digits + if use_specials: + character_set = character_set + specials + + if not character_set: + return "Error: No character sets selected. Cannot generate password." + + password = '' + for i in range(length): + password = password + random.choice(character_set) + return password + +# STEP 4: Check password strength +def check_strength(length, use_lower, use_upper, use_digits, use_specials): + score = 0 + + # Add points for character variety + score = score + use_lower + use_upper + use_digits + use_specials + + if length >= 12: + score = score + 1 + + if score <= 2: + return "Weak" + elif score == 3 or score == 4: + return "Medium" + else: + return "Strong" + +# STEP 5: Command-line interface using argparse +def main(): + parser = argparse.ArgumentParser(description="πŸ” Password Generator Tool") + + parser.add_argument('--length', type=int, required=True, help='Password length (e.g., 8, 12, 16)') + parser.add_argument('--label', type=str, required=True, help='Purpose or label for the password (e.g, Google, Gmail)') + parser.add_argument('--lower', action='store_true', help='Include lowercase letters') + parser.add_argument('--upper', action='store_true', help='Include uppercase letters') + parser.add_argument('--digits', action='store_true', help='Include digits') + parser.add_argument('--specials', action='store_true', help='Include special characters') + + args = parser.parse_args() + + # Validate length + if args.length <= 0: + print("❌ Password length must be positive. Try again.") + return + + # Generate and evaluate password + password = generate_password(args.length, args.lower, args.upper, args.digits, args.specials) + if password.startswith("Error"): + print(password) + return + + print(f"βœ… Your generated password is: {password}") + strength = check_strength(args.length, args.lower, args.upper, args.digits, args.specials) + print(f"πŸ’ͺ Password Strenght: {strength}") + + # STEP 6: Encrypt password using Fernet + key = load_key() + fernet = Fernet(key) + encrypted_password = fernet.encrypt(password.encode()).decode() # This variable already holds the Fernet encrypted string. + + # STEP 7: Save encrypted password to file + with open("saved_passwords.txt", "a") as file: + timestamp = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S") + # The 'encoded_password' (base64 of original password) was being saved previously. + # We should save the 'encrypted_password' (Fernet encrypted string) instead. + file.write(f"\n[{timestamp}]\n") + file.write(f"Label: {args.label}\n") + file.write(f"Encrypted Password: {encrypted_password}\n") # Save the Fernet encrypted password and use "Encrypted Password:" label + file.write(f"Included - Lowercase: {args.lower}, Uppercase: {args.upper}, Digits: {args.digits}, Special Characters: {args.specials}\n") + file.write("-" * 40 + "\n") + + print("πŸ”’ Password encrypted and saved to 'saved_passwords.txt") + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Secure_Password_Manager/cli/view_passwords_cli.py b/Secure_Password_Manager/cli/view_passwords_cli.py new file mode 100644 index 0000000..06eefa5 --- /dev/null +++ b/Secure_Password_Manager/cli/view_passwords_cli.py @@ -0,0 +1,48 @@ +import base64 + +def view_passwords(filename="saved_passwords.txt"): + try: + with open(filename, "r") as file: + lines = file.readlines() + + # Temperory variable to store each password block info + timestamp = label = encoded_password = "" + included_options = "" + + print("\nπŸ” Saved Passwords:\n") + + for line in lines: + line = line.strip() + + if line.startswith("["): # Timestamp line + timestamp = line.strip("[]") + + elif line.startswith("Label:"): + label = line.split("Label:")[1].strip() + + elif line.startswith("Encoded Password:"): + encoded_password = line.split("Encoded Password:")[1].strip() + try: + decoded_password = base64.b64decode(encoded_password.encode()).decode() + except Exception as e: + decoded_password = f"[Error decoding password: {e}]" + + elif line.startswith("Included"): + included_options = line.split("Included -")[1].strip() + + elif line.startswith("-" * 10): # Block ends here + print(f"πŸ“… Date/Time: {timestamp}") + print(f"🏷️ Label: {label}") + print(f"πŸ”“ Decoded Password: {decoded_password}") + print(f"πŸ”§ Options Included: {included_options}") + print("-" * 40) + + except FileNotFoundError: + print("❌ saved_passwords.txt not found.") + + except Exception as e: + print(f"❌ An error occured: {e}") + +# Run the function +if __name__ == '__main__': + view_passwords() \ No newline at end of file diff --git a/Secure_Password_Manager/gui/password_manager_gui.py b/Secure_Password_Manager/gui/password_manager_gui.py new file mode 100644 index 0000000..492e7dc --- /dev/null +++ b/Secure_Password_Manager/gui/password_manager_gui.py @@ -0,0 +1,258 @@ +# Import necessary libraries +import os, random, datetime +from tkinter import * +from tkinter import messagebox +from cryptography.fernet import Fernet + +# ------------------------ +# FUNCTION DEFINITIONS +# ------------------------ + +# Generate password based on user options +def generate_password(length, use_lower, use_upper, use_digits, use_specials): + lowercase = 'abcdefghijklmnopqrstuvwxyz' + uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + digits = '0123456789' + specials = '!@#$%^&*()' + + character_set = '' + if use_lower: character_set += lowercase + if use_upper: character_set += uppercase + if use_digits: character_set += digits + if use_specials: character_set += specials + + if not character_set: + return "Error: No character sets selected. Cannot generate password." + + password = ''.join(random.choice(character_set) for _ in range(length)) + return password + +# Check password strength +def check_strength(length, use_lower, use_upper, use_digits, use_specials): + score = use_lower + use_upper + use_digits + use_specials + if length >= 12: + score += 1 + + if score <= 2: + return "Weak" + elif score in (3, 4): + return "Medium" + else: + return "Strong" + +# Load or create encryption key +def load_key(): + key_path = "secret.key" + + if not os.path.exists(key_path): + key = Fernet.generate_key() + with open(key_path, "wb") as key_file: + key_file.write(key) + print("πŸ” Key generated.") + return key + else: + with open(key_path, "rb") as key_file: + key = key_file.read() + + # βœ… Verify it's a valid Fernet key + try: + Fernet(key) # This will raise ValueError if invalid + print("πŸ”‘ Valid key loaded.") + return key + except ValueError: + print("❌ Invalid key detected. Regenerating...") + key = Fernet.generate_key() + with open(key_path, "wb") as key_file: + key_file.write(key) + print("πŸ” New key generated.") + return key + +# Encrypt the password +def encrypt_password(password, key): + fernet = Fernet(key) + return fernet.encrypt(password.encode()).decode() + +# Button click logic +def on_generate_click(): + length_input = password_length_entry.get().strip() + label = purpose_entry.get().strip() or "Unnamed" + lower = use_lower.get() + upper = use_upper.get() + digits = use_digits.get() + specials = use_specials.get() + + # Handle default or invalid input safely + if length_input == "": + length = 12 + elif length_input.isdigit(): + length = int(length_input) + if length <= 0: + messagebox.showerror("Invalid Input", "Password length must be a positive number.") + return + else: + messagebox.showerror("Invalid Input", "Enter a valid number for length.") + return + + # Generate the password + password = generate_password(length, lower, upper, digits, specials) + if password.startswith("Error"): + messagebox.showwarning("Oops", password) + return + + strength = check_strength(length, lower, upper, digits, specials) + + # Show result popup + messagebox.showinfo( + "Password Generated", + f"πŸ” Label: {label}\n\nPassword: {password}\n\nπŸ’ͺ Strength: {strength}" + ) + + # Save to file (encrypted) + key = load_key() + encrypted = encrypt_password(password, key) + + with open("saved_passwords.txt", "a") as file: + timestamp = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S") + file.write(f"\n[{timestamp}]\n") + file.write(f"Label: {label}\n") + file.write(f"Encrypted Password: {encrypted}\n") + file.write(f"Strength: {strength}\n") + file.write("-" * 40 + "\n") + + messagebox.showinfo("βœ… Saved", "Password saved securely to file.") + +def on_view_passwords_click(): + try: + key = load_key() + fernet = Fernet(key) + + # Check if saved passwords file exists + if not os.path.exists("saved_passwords.txt"): + messagebox.showinfo("No Data", "No saved passwords found.") + return + + # Read the file + with open("saved_passwords.txt", "r") as file: + lines = file.readlines() + + display_data = [] + current = {} + + # Parse each line + for line in lines: + line = line.strip() + if line.startswith('['): # timestamp line + current["Time"] = line + elif line.startswith("Label:"): + current["Label"] = line.replace("Label: ", "") + elif line.startswith("Encrypted Password:"): + encrypted = line.replace("Encrypted Password: ", "") + try: + decrypted = fernet.decrypt(encrypted.encode()).decode() + except Exception: + decrypted = "❌ Could not decrypt" + current["Password"] = decrypted + elif line.startswith("Strength:"): + current["Strength"] = line.replace("Strength: ", "") + elif line.startswith("-"): + # end of entry + display_data.append(current) + current = {} + + # If nothing parsed + if not display_data: + messagebox.showinfo("Empty", "No valid entried found.") + return + + # -------------------- + # New GUI Window to Display Saved Passwords + # -------------------- + top = Toplevel(window) + top.title("Saved Passwords") + top.geometry("800x500") + top.config(bg="dark slate gray") + + # Create scrollbar + scrollbar = Scrollbar(top) + scrollbar.pack(side=RIGHT, fill=Y) + + # Create text area and bind it to scrollbar + text = Text(top, wrap=WORD, yscrollcommand=scrollbar.set, font="Consolas 12", bg="black", fg="light green") + text.pack(expand=True, fill=BOTH) + + for entry in display_data: + text.insert(END, f"{entry['Time']}\n") + text.insert(END, f"πŸ”– Label: {entry['Label']}\n") + text.insert(END, f"πŸ” Password: {entry['Password']}\n") + text.insert(END, f"πŸ’ͺ Strength: {entry['Strength']}\n") + text.insert(END, "-" * 40 + "\n\n") + + text.config(state=DISABLED) + scrollbar.config(command=text.yview) + + except Exception as e: + messagebox.showerror("Error", f"Something went wrong:\n{str(e)}") + +# ------------------------ +# GUI LAYOUT +# ------------------------ + +# Create main window +window = Tk() +window.title("Password Book") +icon = PhotoImage(file="icons8-password-book-24.png") +window.iconphoto(False, icon) +window.geometry('900x700') +window.configure(bg='dark slate gray') +window.resizable(False, False) + +# Create main frame +frame = Frame(bg='dark slate gray') + +# Welcome text +welcome_label = Label(frame, text="Welcome to your Password Manager!", bg='dark slate gray', fg="azure", font="fixedsys 30 bold") +welcome_label.grid(row=0, column=0, columnspan=2, sticky="news", pady=40) + +# Label entry +purpose_label = Label(frame, text="Enter a label or purpose for this password:", bg='dark slate gray', fg="ivory2", font=("Arial", 16)) +purpose_label.grid(row=1, column=0) + +purpose_entry = Entry(frame, font=("Arial", 16)) +purpose_entry.grid(row=1, column=1, pady=20) + +# Length entry +password_length_label = Label(frame, text="Enter desired password length:", bg='dark slate gray', fg="ivory2", font=("Arial", 16)) +password_length_label.grid(row=2, column=0) + +password_length_entry = Entry(frame, font=("Arial", 16)) +password_length_entry.grid(row=2, column=1, pady=20) + +# Optional placeholder label (recommended) +default_note = Label(frame, text="*Default is 12 if left empty", bg='dark slate gray', fg="gray80", font=("Arial", 10)) +default_note.grid(row=3, column=1, sticky='w') + +# Character set checkboxes +use_lower = BooleanVar() +use_upper = BooleanVar() +use_digits = BooleanVar() +use_specials = BooleanVar() + +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)) +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)) +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)) +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)) + +# Generate button +generate_btn = Button(frame, text="Generate Password", bg="slate gray", fg="ivory2", font="Arial 16 bold", command=on_generate_click) +generate_btn.grid(row=8, column=0, columnspan=2, pady=30) + +# View passwords button +view_btn = Button(frame, text="View Saved Passwords", bg="slate gray", fg="ivory2", font="Arial 16 bold") +view_btn.grid(row=9, column=0, columnspan=2) +view_btn.config(command=on_view_passwords_click) + +# Pack the frame +frame.pack() + +# Start the app +window.mainloop() diff --git a/Secure_Password_Manager/icons8-password-book-24.png b/Secure_Password_Manager/icons8-password-book-24.png new file mode 100644 index 0000000..508276e Binary files /dev/null and b/Secure_Password_Manager/icons8-password-book-24.png differ