migrate to git.charlotte.sh
This commit is contained in:
commit
fbd588721e
412 changed files with 13750 additions and 0 deletions
389
python-csi160/password-manager/password-manager.py
Normal file
389
python-csi160/password-manager/password-manager.py
Normal file
|
@ -0,0 +1,389 @@
|
|||
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}")
|
Loading…
Add table
Add a link
Reference in a new issue