diff --git a/PASSWORDMANAGE/README.md b/PASSWORDMANAGE/README.md new file mode 100644 index 0000000..079fd97 --- /dev/null +++ b/PASSWORDMANAGE/README.md @@ -0,0 +1,120 @@ + +# ๐Ÿ” Password Manager + +A secure desktop application to store and manage website credentials. It provides an intuitive UI, encrypted password storage, and basic CRUD functionality using SQLite and Python. + +--- + +## ๐Ÿ“Œ Features + +### โœ… Core Functionality +- **Master Password Login**: Access is protected with a configurable master password. +- **Add Records**: Securely add website credentials (website, username, password). +- **View Records**: Display all saved records in a sortable Treeview. +- **Update/Delete Records**: Modify or remove existing entries. +- **Copy Password**: Quickly copy passwords to clipboard with one click. +- **Encrypted Passwords**: Passwords are stored securely using `cryptography.Fernet`. +- **Masked Input**: Password entry fields are hidden by default. +- **Popup Notifications**: Temporary feedback for successful operations or errors. + +--- + +## ๐Ÿ“ Project Structure + +```plaintext +โ”œโ”€โ”€ password manager.py # Main GUI application +โ”œโ”€โ”€ db_operations.py # Handles database and encryption logic +โ”œโ”€โ”€ secret.key # Auto-generated key for encrypting passwords +โ”œโ”€โ”€ password_records.db # Local SQLite database (auto-created) +``` + +--- + +## ๐Ÿ”’ Security & Encryption + +- Uses `cryptography.Fernet` for symmetric encryption (AES). +- A unique encryption key is generated and stored in `secret.key`. +- Only the password field is encrypted in the database. +- `secret.key` is critical; if lost, encrypted passwords become unrecoverable. + +--- + +## ๐Ÿ›  Setup Instructions + +### ๐Ÿ“ฆ Requirements +- Python 3.7 or higher +- Required Python packages: + ```bash + pip install cryptography + ``` + +### โ–ถ How to Run +```bash +python "password manager.py" +``` + +--- + +## ๐Ÿ” Master Password + +The master password is **hardcoded** in the application for demonstration purposes. + +- The default master password is: + ``` + admin123 + ``` +- **To change** the master password, edit the following line in `password manager.py`: + ```python + if entered_password == "admin123": + ``` + +--- + +## ๐Ÿšง Planned Enhancements + +- [ ] Implement search functionality for website/username +- [ ] Add master password change feature +- [ ] Include password strength meter +- [ ] Integrate a password generator +- [ ] Export/import database with encryption +- [ ] Toggle password visibility in entry field + +--- + +## ๐Ÿงช Testing + +You can test the current features by: +1. Running the application +2. Logging in using the master password (`admin123`) +3. Adding new records and verifying encryption by checking the `password_records.db` +4. Testing copy password and update/delete functionality + +--- + +## ๐Ÿง  Usage Tips + +- To reset encryption (for testing), delete the `secret.key` and `password_records.db` files. + โš  **This will erase all stored data!** + +- Keep `secret.key` safe and never share it. Losing this file means losing access to all encrypted passwords. + +--- + +## ๐Ÿ‘จโ€๐Ÿ’ป Authors + +Developed with Python by a security-conscious developer using: +- `tkinter` for GUI +- `sqlite3` for local storage +- `cryptography` for encryption + +--- + +## โš  Disclaimer + +This tool is intended for educational and personal use. For handling sensitive credentials, professional-grade tools (like Bitwarden or 1Password) are recommended. + +--- + +## ๐Ÿ“œ License + +This project is licensed under the MIT License. See `LICENSE` for details. diff --git a/PASSWORDMANAGE/__pycache__/db_operations.cpython-312.pyc b/PASSWORDMANAGE/__pycache__/db_operations.cpython-312.pyc new file mode 100644 index 0000000..b199ae5 Binary files /dev/null and b/PASSWORDMANAGE/__pycache__/db_operations.cpython-312.pyc differ diff --git a/PASSWORDMANAGE/db_operations.py b/PASSWORDMANAGE/db_operations.py new file mode 100644 index 0000000..093945f --- /dev/null +++ b/PASSWORDMANAGE/db_operations.py @@ -0,0 +1,96 @@ +import sqlite3 +from cryptography.fernet import Fernet + + +class DbOperations: + + def __init__(self): + self.key = self.load_key() + self.fernet = Fernet(self.key) + + def load_key(self): + try: + with open("secret.key", "rb") as f: + return f.read() + except FileNotFoundError: + key = Fernet.generate_key() + with open("secret.key", "wb") as f: + f.write(key) + return key + + def connect_to_db(self): + conn = sqlite3.connect('password_records.db') + return conn + + def create_table(self, table_name="password_info"): + conn = self.connect_to_db() + query = f''' + CREATE TABLE IF NOT EXISTS {table_name} ( + ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + website TEXT NOT NULL, + username VARCHAR(200), + password TEXT + ); + ''' + with conn as conn: + cursor = conn.cursor() + cursor.execute(query) + + def create_record(self, data, table_name="password_info"): + website = data['website'] + username = data['username'] + password = self.fernet.encrypt(data['password'].encode()) #Encrypt password + conn = self.connect_to_db() + query = f''' + INSERT INTO {table_name} (website, username, password) VALUES (?, ?, ?); + ''' + with conn as conn: + cursor = conn.cursor() + cursor.execute(query, (website, username, password)) + + def show_records(self, table_name="password_info"): + conn = self.connect_to_db() + query = f''' + SELECT * FROM {table_name}; + ''' + with conn as conn: + cursor = conn.cursor() + list_records = cursor.execute(query).fetchall() + + decrypted_records = [] + for row in list_records: + decrypted_password = self.fernet.decrypt(row[5]).decode() + # row: (ID, created_date, update_date, website, username, password) + decrypted_records.append(( + row[0], # ID + row[1], # created_date + row[2], # update_date + row[3], # website + row[4], # username + decrypted_password # decrypted password + )) + return decrypted_records + + def update_record(self, data, table_name="password_info"): + ID = data['ID'] + website = data['website'] + username = data['username'] + password = self.fernet.encrypt(data['password'].encode()) #Encrypt updated password + conn = self.connect_to_db() + query = f''' + UPDATE {table_name} SET website = ?, username = ?, password = ? WHERE ID = ?; + ''' + with conn as conn: + cursor = conn.cursor() + cursor.execute(query, (website, username, password, ID)) + + def delete_record(self, ID, table_name="password_info"): + conn = self.connect_to_db() + query = f''' + DELETE FROM {table_name} WHERE ID = ?; + ''' + with conn as conn: + cursor = conn.cursor() + cursor.execute(query, (ID,)) diff --git a/PASSWORDMANAGE/password manager.py b/PASSWORDMANAGE/password manager.py new file mode 100644 index 0000000..f5a840c --- /dev/null +++ b/PASSWORDMANAGE/password manager.py @@ -0,0 +1,182 @@ +#Password manager +from tkinter import Tk, Label, Button, Entry, Frame, END, Toplevel +from tkinter import ttk +from db_operations import DbOperations + + +class LoginWindow: + def __init__(self, root, on_success): + self.root = root + self.root.title("Login - Master Password") + self.root.geometry("300x120+500+300") + self.on_success = on_success + + Label(root, text="Enter Master Password", font=("Times New Roman", 12)).pack(pady=5) + self.password_entry = Entry(root, show="*", width=25, font=("Times New Roman", 12)) + self.password_entry.pack(pady=5) + + Button(root, text="Login", font=("Times New Roman", 12), command=self.check_password).pack(pady=5) + + def check_password(self): + entered_password = self.password_entry.get() + if entered_password == "admin123": # You can change the master password here + self.root.destroy() # Close login window + self.on_success() # Launch the actual app + else: + self.password_entry.delete(0, END) + self.password_entry.insert(0, "") + self.password_entry.config(bg="misty rose") + + + +class root_window: + + def __init__(self, root, db): + self.db= db + self.root= root + self.root.title("Passowrd Manager") + self.root.geometry("924x550+300+40") + + head_title= Label(self.root, text= "Password Manager", width=42, + bg= "royal blue", font=("Times New Roman", 20), fg= "white" , padx=150, pady=10).grid(columnspan=4) + + self.crud_frame= Frame(self.root, highlightbackground="black", highlightthickness=3, padx=7, pady=10) + self.crud_frame.grid() + self.create_entry_labels() + self.create_entry_boxes() + self.create_crud_buttons() + self.search_entry= Entry(self.crud_frame, width=30, font=('Times New Roman', 12)) + self.search_entry.grid(row=self.row_no, column=self.col_no) + self.col_no+=1 + Button(self.crud_frame, text='Search', bg='yellow', font=('Times New Roman', 12), width=22).grid(row=self.row_no, column=self.col_no, padx=5, pady=5) + + self.create_records_tree() + + + + def create_entry_labels(self): + self.col_no, self.row_no= 0, 0 + labels_info= ('ID', 'Website', 'Username', 'Password') + for label_info in labels_info: + Label(self.crud_frame, text=label_info, bg='grey', fg="white", font= ("Times New Roman", 15), padx= 5, pady=2).grid(row=self.row_no, column=self.col_no, padx= 5, pady=2) + self.col_no +=1 + + + def create_crud_buttons(self): + self.row_no+=1 + self.col_no= 0 + buttons_info= (('Save', 'green', self.save_record), ('Update', 'blue', self.update_record), ('Delete', 'red', self.delete_record), ('Copy Password', 'purple', self.copy_password), ('Show All Records', 'Teal', self.show_records)) + for btn_info in buttons_info: + if btn_info[0]== 'Show All Records': + self.row_no+=1 + self.col_no=0 + Button(self.crud_frame, text=btn_info[0], bg=btn_info[1], fg="white", font= ("Times New Roman", 15), padx= 2, pady=1, width=18, command= btn_info[2]).grid(row=self.row_no, column=self.col_no, padx= 5, pady=2) + self.col_no +=1 + + + def create_entry_boxes(self): + self.row_no+=1 + self.entry_boxes= [] + self.col_no = 0 + for i in range(4): + show="" + if i == 3: + show = "*" + entry_box= Entry(self.crud_frame, width=21, bg= "lightgrey", font=("Times New Roman", 15), show=show) + entry_box.grid(row= self.row_no, column= self.col_no, padx=1, pady=2) + self.col_no+=1 + self.entry_boxes.append(entry_box) + + #CRUD Functions + + def save_record(self): + website= self.entry_boxes[1].get() + username= self.entry_boxes[2].get() + password= self.entry_boxes[3].get() + data= {'website': website, 'username': username, 'password': password} + self.db.create_record(data) + self.show_records() + + def update_record(self): + ID= self.entry_boxes[0].get() + website= self.entry_boxes[1].get() + username= self.entry_boxes[2].get() + password= self.entry_boxes[3].get() + data= {'ID': ID, 'website': website, 'username': username, 'password': password} + self.db.update_record(data) + self.show_records() + + def delete_record(self): + ID= self.entry_boxes[0].get() + self.db.delete_record(ID) + self.show_records() + + def show_records(self): + for item in self.records_tree.get_children(): + self.records_tree.delete(item) + records_list = self.db.show_records() + for record in records_list: + self.records_tree.insert('', END, values=(record[0], record[3], record[4], record[5])) + + def create_records_tree(self): + columns= ('ID', 'Website', 'Username', 'Password') + self.records_tree= ttk.Treeview(self.root, columns=columns, show='headings') + self.records_tree.heading('ID', text='ID') + self.records_tree.heading('Website', text='Website Name') + self.records_tree.heading('Username', text='Username') + self.records_tree.heading('Password', text='Password') + self.records_tree['displaycolumns']= ('Website', 'Username', 'Password') + self.records_tree.grid() + + def item_selected(event): + for selected_item in self.records_tree.selection(): + item= self.records_tree.item(selected_item) + record= item['values'] + for entry_box, item in zip(self.entry_boxes, record): + entry_box.delete(0, END) + entry_box.insert(0, item) + + self.records_tree.bind('<>', item_selected) + + self.records_tree.grid() + + + #Copy to Clipboard + def copy_password(self): + self.root.clipboard_clear() + self.root.clipboard_append(self.entry_boxes[3].get()) + message= "Password Copied" + title= "Copy" + if self.entry_boxes[3].get()=="": + message= "Box is empty" + title= "Error" + self.showmessage(title, message) + + def showmessage(self, title_box:str=None, message:str=None): + TIME_TO_WAIT= 900 #in milliseconds + root= Toplevel(self.root) + background='green' + if title_box== "Error": + background= "red" + root.geometry('200x30+600+200') + root.title(title_box) + Label(root, text=message, background=background, font=("Times New Roman", 15), fg='white').pack(padx=4, pady=2) + try: + root.after(TIME_TO_WAIT, root.destroy) + except Exception as e: + print("Error occured", e) + + + + +def launch_main_app(): + root = Tk() + db_class = DbOperations() + db_class.create_table() + root_class = root_window(root, db_class) + root.mainloop() + +if __name__ == "__main__": + login_root = Tk() + LoginWindow(login_root, on_success=launch_main_app) + login_root.mainloop() diff --git a/PASSWORDMANAGE/password_records.db b/PASSWORDMANAGE/password_records.db new file mode 100644 index 0000000..a1e7c09 Binary files /dev/null and b/PASSWORDMANAGE/password_records.db differ diff --git a/PASSWORDMANAGE/secret.key b/PASSWORDMANAGE/secret.key new file mode 100644 index 0000000..8a305ce --- /dev/null +++ b/PASSWORDMANAGE/secret.key @@ -0,0 +1 @@ +zcnj1Ex1bjddCvAW7D7b5FM5UCk4k2a3aQwQuytIolI= \ No newline at end of file