diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cc06ec8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,135 @@
+# 🐨 KeyKoala
+
+
+
+
+
+A secure, lightweight, and user-friendly password manager built with Python.
+
+[](https://opensource.org/licenses/MIT)
+[](https://www.python.org/downloads/)
+[](https://github.com/psf/black)
+
+
+
+## 🚀 Features
+
+- 🔐 Master password protection
+- 🔒 Strong encryption using Fernet
+- 📋 Quick copy passwords to clipboard
+- 💾 Secure local storage
+- 🖥️ Clean, intuitive GUI
+- 🏃 Lightweight and fast
+- 📱 Cross-platform support
+
+## 🛠️ Installation
+
+1. Clone the repository
+```bash
+git clone https://github.com/yourusername/keykoala.git
+cd keykoala
+```
+
+2. Create and activate virtual environment
+```bash
+python -m venv venv
+source venv/bin/activate # On Windows: venv\Scripts\activate
+```
+
+3. Install dependencies
+```bash
+pip install -r requirements.txt
+```
+
+4. Install tkinter (if not already installed)
+
+**Ubuntu/Debian:**
+```bash
+sudo apt-get install python3-tk
+```
+
+**Fedora:**
+```bash
+sudo dnf install python3-tkinter
+```
+
+**macOS:**
+```bash
+brew install python-tk
+```
+
+## 🏃♂️ Running KeyKoala
+
+From the project root directory:
+```bash
+PYTHONPATH=$PYTHONPATH:$(pwd) python src/main.py
+```
+
+## 🔒 Security Features
+
+- Strong encryption using Fernet (symmetric encryption)
+- Master password never stored, only used for key derivation
+- Passwords only decrypted when copying to clipboard
+- All data stored locally on your machine
+
+## 🤝 Contributing
+
+Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
+
+1. Fork the repository
+2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
+3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
+4. Push to the branch (`git push origin feature/AmazingFeature`)
+5. Open a Pull Request
+
+## 📝 Development Tasks
+
+- [ ] Password generation functionality
+- [ ] Password strength checker
+- [ ] Import/export functionality
+- [ ] Dark mode support
+- [ ] Password categories/folders
+- [ ] Auto-logout feature
+- [ ] Two-factor authentication
+- [ ] Browser extension
+
+## 🐛 Known Issues
+
+- None reported yet
+
+## 📦 Dependencies
+
+- Python 3.7+
+- tkinter
+- cryptography
+- pyperclip
+
+## ⚠️ Disclaimer
+
+KeyKoala is a demonstration project and should not be used as your primary password manager without thorough security auditing. For sensitive information, please use established password managers like Bitwarden, LastPass, or 1Password.
+
+## 📄 License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+
+## 🙏 Acknowledgments
+
+- Inspired by modern password managers
+- Built with Python's cryptography library
+- Uses tkinter for the GUI
+
+## 📧 Contact
+
+- Project Maintainer: [Blake Ridgway](mailto:blake@blakeridgway.com)
+- Project Link: [https://github.com/blakeridgway/keykoala](https://github.com/blakeridgway/keykoala)
+
+## 🌟 Support
+
+If you find this project helpful, please give it a star! ⭐
+
+---
+
+
+Made with ❤️ by Blake Ridgway
+
+```
\ No newline at end of file
diff --git a/assets/images/keykoala_logo.png b/assets/images/keykoala_logo.png
new file mode 100644
index 0000000..9863b3a
Binary files /dev/null and b/assets/images/keykoala_logo.png differ
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..176e3e6
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+cryptography>=3.4.7
+pyperclip>=1.8.2
\ No newline at end of file
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/crypto/__init__.py b/src/crypto/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/crypto/encryption.py b/src/crypto/encryption.py
new file mode 100644
index 0000000..634e6f4
--- /dev/null
+++ b/src/crypto/encryption.py
@@ -0,0 +1,28 @@
+# src/crypto/encryption.py
+import base64
+from cryptography.fernet import Fernet
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+
+class EncryptionManager:
+ def __init__(self, master_password, salt):
+ self.fernet = self._create_fernet(master_password, salt)
+
+ def _create_fernet(self, master_password, salt):
+ """Create a Fernet instance using the master password and salt."""
+ kdf = PBKDF2HMAC(
+ algorithm=hashes.SHA256(),
+ length=32,
+ salt=salt,
+ iterations=480000,
+ )
+ key = base64.urlsafe_b64encode(kdf.derive(master_password.encode()))
+ return Fernet(key)
+
+ def encrypt(self, data):
+ """Encrypt the data."""
+ return self.fernet.encrypt(data.encode()).decode()
+
+ def decrypt(self, encrypted_data):
+ """Decrypt the data."""
+ return self.fernet.decrypt(encrypted_data.encode()).decode()
diff --git a/src/gui/__init__.py b/src/gui/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/gui/app.py b/src/gui/app.py
new file mode 100644
index 0000000..4a1f3b9
--- /dev/null
+++ b/src/gui/app.py
@@ -0,0 +1,44 @@
+import tkinter as tk
+from gui.login_screen import LoginScreen
+from gui.main_screen import MainScreen
+from crypto.encryption import EncryptionManager
+from utils.storage import StorageManager
+
+class PasswordManager:
+ def __init__(self):
+ self.root = tk.Tk()
+ self.root.title("KeyKoala")
+ self.root.geometry("600x800")
+
+ self.storage_manager = StorageManager()
+ self.encryption_manager = None
+ self.current_screen = None
+
+ self.setup_gui()
+
+ def setup_gui(self):
+ self.show_login_screen()
+
+ def show_login_screen(self):
+ if self.current_screen:
+ self.current_screen.destroy()
+ self.current_screen = LoginScreen(self.root, self.on_login)
+
+ def show_main_screen(self):
+ if self.current_screen:
+ self.current_screen.destroy()
+ self.current_screen = MainScreen(
+ self.root,
+ self.encryption_manager,
+ self.storage_manager
+ )
+
+ def on_login(self, master_password):
+ self.encryption_manager = EncryptionManager(
+ master_password,
+ self.storage_manager.get_salt()
+ )
+ self.show_main_screen()
+
+ def run(self):
+ self.root.mainloop()
\ No newline at end of file
diff --git a/src/gui/login_screen.py b/src/gui/login_screen.py
new file mode 100644
index 0000000..d685c58
--- /dev/null
+++ b/src/gui/login_screen.py
@@ -0,0 +1,24 @@
+import tkinter as tk
+from tkinter import ttk
+
+class LoginScreen(ttk.Frame):
+ def __init__(self, parent, login_callback):
+ super().__init__(parent, padding="20")
+ self.login_callback = login_callback
+ self.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ self.setup_gui()
+
+ def setup_gui(self):
+ ttk.Label(self, text="Master Password:").grid(row=0, column=0, pady=10)
+ self.password_entry = ttk.Entry(self, show="*")
+ self.password_entry.grid(row=0, column=1, pady=10)
+
+ ttk.Button(
+ self,
+ text="Login",
+ command=self.handle_login
+ ).grid(row=1, column=0, columnspan=2, pady=10)
+
+ def handle_login(self):
+ master_password = self.password_entry.get()
+ self.login_callback(master_password)
diff --git a/src/gui/main_screen.py b/src/gui/main_screen.py
new file mode 100644
index 0000000..bf4fa38
--- /dev/null
+++ b/src/gui/main_screen.py
@@ -0,0 +1,111 @@
+import tkinter as tk
+from tkinter import ttk, messagebox
+import pyperclip
+
+class MainScreen(ttk.Frame):
+ def __init__(self, parent, encryption_manager, storage_manager):
+ super().__init__(parent, padding="20")
+ self.encryption_manager = encryption_manager
+ self.storage_manager = storage_manager
+ self.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+ self.setup_gui()
+
+ def setup_gui(self):
+ self.create_entry_fields()
+ self.create_treeview()
+ self.create_context_menu()
+ self.load_entries()
+
+ def create_entry_fields(self):
+ ttk.Label(self, text="Site:").grid(row=0, column=0, pady=5)
+ self.site_entry = ttk.Entry(self)
+ self.site_entry.grid(row=0, column=1, pady=5)
+
+ ttk.Label(self, text="Username:").grid(row=1, column=0, pady=5)
+ self.username_entry = ttk.Entry(self)
+ self.username_entry.grid(row=1, column=1, pady=5)
+
+ ttk.Label(self, text="Password:").grid(row=2, column=0, pady=5)
+ self.password_entry = ttk.Entry(self, show="*")
+ self.password_entry.grid(row=2, column=1, pady=5)
+
+ ttk.Button(
+ self,
+ text="Add Entry",
+ command=self.add_entry
+ ).grid(row=3, column=0, columnspan=2, pady=10)
+
+ def create_treeview(self):
+ columns = ("Site", "Username")
+ self.tree = ttk.Treeview(self, columns=columns, show="headings")
+
+ for col in columns:
+ self.tree.heading(col, text=col)
+ self.tree.column(col, width=100)
+
+ self.tree.grid(row=4, column=0, columnspan=2, pady=10)
+
+ def create_context_menu(self):
+ self.popup_menu = tk.Menu(self, tearoff=0)
+ self.popup_menu.add_command(
+ label="Copy Password",
+ command=self.copy_password
+ )
+ self.popup_menu.add_command(
+ label="Delete Entry",
+ command=self.delete_entry
+ )
+
+ self.tree.bind("", self.show_popup_menu)
+
+ def show_popup_menu(self, event):
+ item = self.tree.identify_row(event.y)
+ if item:
+ self.tree.selection_set(item)
+ self.popup_menu.post(event.x_root, event.y_root)
+
+ def add_entry(self):
+ site = self.site_entry.get()
+ username = self.username_entry.get()
+ password = self.password_entry.get()
+
+ if not all([site, username, password]):
+ messagebox.showerror("Error", "All fields are required!")
+ return
+
+ encrypted_password = self.encryption_manager.encrypt(password)
+ self.storage_manager.add_entry(site, username, encrypted_password)
+ self.load_entries()
+ self.clear_entries()
+
+ def clear_entries(self):
+ self.site_entry.delete(0, tk.END)
+ self.username_entry.delete(0, tk.END)
+ self.password_entry.delete(0, tk.END)
+
+ def load_entries(self):
+ self.tree.delete(*self.tree.get_children())
+ entries = self.storage_manager.get_all_entries()
+
+ for site, data in entries.items():
+ self.tree.insert("", tk.END, values=(site, data["username"]))
+
+ def copy_password(self):
+ selected = self.tree.selection()
+ if not selected:
+ return
+
+ site = self.tree.item(selected[0])["values"][0]
+ encrypted_password = self.storage_manager.get_password(site)
+ decrypted_password = self.encryption_manager.decrypt(encrypted_password)
+ pyperclip.copy(decrypted_password)
+ messagebox.showinfo("Success", "Password copied to clipboard!")
+
+ def delete_entry(self):
+ selected = self.tree.selection()
+ if not selected:
+ return
+
+ site = self.tree.item(selected[0])["values"][0]
+ self.storage_manager.delete_entry(site)
+ self.load_entries()
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..6a7359d
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,8 @@
+from gui.app import PasswordManager
+
+def main():
+ app = PasswordManager()
+ app.run()
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/src/utils/__init__.py b/src/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/utils/storage.py b/src/utils/storage.py
new file mode 100644
index 0000000..0790ad3
--- /dev/null
+++ b/src/utils/storage.py
@@ -0,0 +1,68 @@
+import json
+import os
+import base64
+import secrets
+
+class StorageManager:
+ def __init__(self, data_file="passwords.enc"):
+ self.data_file = data_file
+ self.config_file = "config.json"
+ self.passwords = {}
+ self.salt = self._get_or_create_salt()
+ self._load_data()
+
+ def _get_or_create_salt(self):
+ """Generate or retrieve the salt from config file."""
+ if os.path.exists(self.config_file):
+ try:
+ with open(self.config_file, "r") as f:
+ config = json.load(f)
+ return base64.b64decode(config["salt"])
+ except Exception:
+ return self._create_new_salt()
+ return self._create_new_salt()
+
+ def _create_new_salt(self):
+ """Generate a new salt and save it to config."""
+ salt = secrets.token_bytes(32) # 32 bytes = 256 bits
+ config = {"salt": base64.b64encode(salt).decode()}
+
+ with open(self.config_file, "w") as f:
+ json.dump(config, f)
+
+ return salt
+
+ def get_salt(self):
+ """Return the current salt."""
+ return self.salt
+
+ def _load_data(self):
+ if os.path.exists(self.data_file):
+ try:
+ with open(self.data_file, "r") as f:
+ return json.load(f)
+ except Exception:
+ return {}
+ return {}
+
+ def _save_data(self):
+ with open(self.data_file, "w") as f:
+ json.dump(self.passwords, f)
+
+ def add_entry(self, site, username, encrypted_password):
+ self.passwords[site] = {
+ "username": username,
+ "password": encrypted_password
+ }
+ self._save_data()
+
+ def get_all_entries(self):
+ return self.passwords
+
+ def get_password(self, site):
+ return self.passwords[site]["password"]
+
+ def delete_entry(self, site):
+ if site in self.passwords:
+ del self.passwords[site]
+ self._save_data()