import pickle import sys import random import string # The password list - We start with it populated for testing purposes entries = {'yahoo': {'username': 'johndoe', 'password': 'cus#u%S tu', 'url': 'https://www.yahoo.com'}, 'google': {'username': 'johndoe', 'password': '`q$$( #tABCD^ %fu#*W t', 'url': 'https://www.google.com'}} # The password file name to store the data to password_file_name = "PasswordFile.pickle" # The encryption key for the caesar cypher encryption_key = 16 menu_text = """ What would you like to do: 1. Open password file 2. Add an entry 3. Lookup an entry 4. Save password file 5. Quit program 6. Print dictionary for testing 7. Delete an entry 8. Edit an entry Please enter a number (1-8)""" def password_encrypt(unencrypted_message, key): """Returns an encrypted message using a caesar cypher :param unencryptedMessage (string) :param key (int) The offset to be used for the caesar cypher :return (string) The encrypted message """ result_string = '' min_limit = 32 max_limit = 126 for character in unencrypted_message: value = ord(character) - min_limit + key value = value % (max_limit - min_limit + 1) value = value + min_limit result_string = result_string + chr(value) return result_string def password_decrypt(encrypted_message, key): """Returns a decrypted message. :param encrypted_message (string): :param key (int) The offset that was used to encrypt the message :return (string): The decrypted message """ return password_encrypt(encrypted_message, -key) def load_password_file(): """Loads a password file. The file must be in the same directory as the .py file """ global entries, encryption_key try: entries, encryption_key = pickle.load(open(password_file_name, "rb")) print(f"Password file '{password_file_name}' loaded successfully!") except FileNotFoundError: print(f"File '{password_file_name}' not found. Starting with an empty dictionary.") entries = {} except Exception as e: print(f"Error loading file: {e}") def save_password_file(): """Saves a password file. The file will be created if it doesn't exist. """ try: pickle.dump((entries, encryption_key), open(password_file_name, "wb")) print(f"Password file '{password_file_name}' saved successfully!") except Exception as e: print(f"Error saving file: {e}") def generate_random_password(length=12): """Generates a random password that meets complexity requirements. The generated password will: - Be at least 8 characters long (default 12) - Contain at least one uppercase letter - Contain at least one lowercase letter - Contain at least one digit - Contain at least one special character :param length (int): Length of the password to generate (minimum 8) :return (string): A randomly generated password meeting all requirements """ if length < 8: length = 8 # Enforce minimum length # Define character sets uppercase_letters = string.ascii_uppercase lowercase_letters = string.ascii_lowercase digits = string.digits special_chars = "!@#$%^&*()-_=+[]{}|;:,.<>?" # Ensure we have at least one of each required character type password = [ random.choice(uppercase_letters), random.choice(lowercase_letters), random.choice(digits), random.choice(special_chars) ] # Fill the rest with random characters from all allowed sets all_chars = uppercase_letters + lowercase_letters + digits + special_chars for _ in range(length - 4): password.append(random.choice(all_chars)) # Shuffle the password random.shuffle(password) # Convert list to string return ''.join(password) def check_password_complexity(password): """Checks if a password meets complexity requirements. Requirements: - At least 8 characters long - Contains at least one uppercase letter - Contains at least one lowercase letter - Contains at least one digit - Contains at least one special character :param password (string): The password to check :return (tuple): (bool, string) - True if the password meets all requirements, False otherwise, and a message explaining the result """ # Check length if len(password) < 8: return False, "Password must be at least 8 characters long." # Check for uppercase letter if not any(char.isupper() for char in password): return False, "Password must contain at least one uppercase letter." # Check for lowercase letter if not any(char.islower() for char in password): return False, "Password must contain at least one lowercase letter." # Check for digit if not any(char.isdigit() for char in password): return False, "Password must contain at least one digit." # Check for special character special_chars = "!@#$%^&*()-_+=<>?/[]{}|\\~`" if not any(char in special_chars for char in password): return False, "Password must contain at least one special character." return True, "Password meets all complexity requirements." def add_entry(): """Adds an entry with an entry title, username, password and url Includes all user interface interactions to get the necessary information from the user """ try: entry_title = input("Enter entry title: ") username = input("Enter username: ") # Explain password requirements print("\nPassword requirements:") print("- At least 8 characters long") print("- Contains at least one uppercase letter") print("- Contains at least one lowercase letter") print("- Contains at least one digit") print("- Contains at least one special character\n") # Ask if user wants to generate a random password gen_password = input("Would you like to generate a random password? (y/n): ") if gen_password.lower() == 'y': # Ask for desired password length try: length = int(input("Enter desired password length (minimum 8, default 12): ") or "12") if length < 8: print("Password length must be at least 8. Setting to 8.") length = 8 except ValueError: print("Invalid input. Using default length of 12.") length = 12 # Generate the password password = generate_random_password(length) print(f"Generated password: {password}") is_valid = True else: # Get and validate manual password print("Enter your password manually:") while True: password = input("Enter password: ") is_valid, message = check_password_complexity(password) if is_valid: print(message) break else: print(message) print("Please try again with a stronger password.") url = input("Enter URL: ") # Encrypt the password encrypted_password = password_encrypt(password, encryption_key) # Add the new entry to the entries dictionary entries[entry_title] = { 'username': username, 'password': encrypted_password, 'url': url } print(f"Entry '{entry_title}' added successfully!") except Exception as e: print(f"Error adding entry: {e}") def print_entry(): """Asks the user for the name of the entry and prints all related information in a pretty format. Includes all information about an entry. """ try: print("Which entry do you want to lookup the information for?") for key in entries: print(key) entry = input('Enter entry name: ') # Check if the entry exists if entry in entries: entry_data = entries[entry] # Decrypt the password decrypted_password = password_decrypt(entry_data['password'], encryption_key) # Print the entry information print("\n--- Entry Information ---") print(f"Entry: {entry}") print(f"Username: {entry_data['username']}") print(f"Password: {decrypted_password}") print(f"URL: {entry_data['url']}") print("------------------------\n") else: print(f"Entry '{entry}' not found!") except KeyError: print(f"Error: The entry does not exist or has incomplete data.") except Exception as e: print(f"Error looking up entry: {e}") def delete_entry(): """Deletes an entry from the password manager. Asks the user for the entry name to delete and removes it if it exists. This is additional feature #1. """ try: print("Which entry do you want to delete?") for key in entries: print(key) entry = input('Enter entry name: ') # Check if the entry exists if entry in entries: # Ask for confirmation confirm = input(f"Are you sure you want to delete '{entry}'? (y/n): ") if confirm.lower() == 'y': # Remove the entry del entries[entry] print(f"Entry '{entry}' deleted successfully!") else: print("Deletion cancelled.") else: print(f"Entry '{entry}' not found!") except Exception as e: print(f"Error deleting entry: {e}") def edit_entry(): """Allows the user to edit an existing entry in the password manager. The user can update the username, password, and/or URL. This is additional feature #2. """ try: print("Which entry do you want to edit?") for key in entries: print(key) entry = input('Enter entry name: ') # Check if the entry exists if entry in entries: entry_data = entries[entry] print(f"\nEditing entry: {entry}") # Get current values for reference current_username = entry_data['username'] current_url = entry_data['url'] # Update username new_username = input(f"Enter new username (current: {current_username}) or press Enter to keep current: ") if new_username: entry_data['username'] = new_username # Update password update_password = input("Do you want to update the password? (y/n): ") if update_password.lower() == 'y': # Explain password requirements print("\nPassword requirements:") print("- At least 8 characters long") print("- Contains at least one uppercase letter") print("- Contains at least one lowercase letter") print("- Contains at least one digit") print("- Contains at least one special character (!@#$%^&*()-_+=<>?/[]{}|\\~`)\n") # Ask if user wants to generate a random password gen_password = input("Would you like to generate a random password? (y/n): ") if gen_password.lower() == 'y': # Ask for desired password length try: length = int(input("Enter desired password length (minimum 8, default 12): ") or "12") if length < 8: print("Password length must be at least 8. Setting to 8.") length = 8 except ValueError: print("Invalid input. Using default length of 12.") length = 12 # Generate the password new_password = generate_random_password(length) print(f"Generated password: {new_password}") # Encrypt the new password entry_data['password'] = password_encrypt(new_password, encryption_key) else: # Get and validate new password manually while True: new_password = input("Enter new password: ") is_valid, message = check_password_complexity(new_password) if is_valid: print(message) # Encrypt the new password entry_data['password'] = password_encrypt(new_password, encryption_key) break else: print(message) print("Please try again with a stronger password.") # Update URL new_url = input(f"Enter new URL (current: {current_url}) or press Enter to keep current: ") if new_url: entry_data['url'] = new_url print(f"Entry '{entry}' updated successfully!") else: print(f"Entry '{entry}' not found!") except Exception as e: print(f"Error editing entry: {e}") def end_program(): """Exits the program. """ sys.exit() def print_dictionary(): """Prints the current entries dictionary. For testing purposes only. """ print(entries) # Menu dictionary mapping user choices to functions menu_dict = {'1': load_password_file, '2': add_entry, '3': print_entry, '4': save_password_file, '5': end_program, '6': print_dictionary, '7': delete_entry, '8': edit_entry} # Main program loop while True: try: user_choice = input(menu_text) if user_choice in menu_dict and menu_dict[user_choice]: menu_dict[user_choice]() else: print('Not a valid choice') except Exception as e: print(f"An error occurred: {e}")