389 lines
No EOL
14 KiB
Python
389 lines
No EOL
14 KiB
Python
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}") |