├── Cifra.py ├── README.md └── requirements.txt /Cifra.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | import sys 4 | from cryptography.fernet import Fernet 5 | from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 6 | QLabel, QLineEdit, QPushButton, QTableWidget, QTableWidgetItem, 7 | QHeaderView, QMessageBox, QFileDialog, QDialog, QInputDialog, 8 | QTabWidget, QGridLayout, QFrame, QSizePolicy, QSpacerItem) 9 | from PyQt5.QtCore import Qt, QSize 10 | from PyQt5.QtGui import QIcon, QFont, QColor, QPalette, QPixmap 11 | import qdarkstyle 12 | 13 | class PasswordEntry(QDialog): 14 | def __init__(self, parent=None, service="", username="", edit_mode=False): 15 | super().__init__(parent) 16 | self.setWindowTitle("Aggiungi Nuovo Servizio" if not edit_mode else "Modifica Servizio") 17 | self.setMinimumWidth(350) 18 | 19 | layout = QVBoxLayout() 20 | 21 | # Service input 22 | service_layout = QHBoxLayout() 23 | service_label = QLabel("Servizio:") 24 | self.service_input = QLineEdit(service) 25 | service_layout.addWidget(service_label) 26 | service_layout.addWidget(self.service_input) 27 | layout.addLayout(service_layout) 28 | 29 | # Username input 30 | username_layout = QHBoxLayout() 31 | username_label = QLabel("Username:") 32 | self.username_input = QLineEdit(username) 33 | username_layout.addWidget(username_label) 34 | username_layout.addWidget(self.username_input) 35 | layout.addLayout(username_layout) 36 | 37 | # Password input 38 | password_layout = QHBoxLayout() 39 | password_label = QLabel("Password:") 40 | self.password_input = QLineEdit() 41 | self.password_input.setEchoMode(QLineEdit.Password) 42 | self.show_password_btn = QPushButton("👁️") 43 | self.show_password_btn.setFixedWidth(30) 44 | self.show_password_btn.clicked.connect(self.toggle_password_visibility) 45 | 46 | password_layout.addWidget(password_label) 47 | password_layout.addWidget(self.password_input) 48 | password_layout.addWidget(self.show_password_btn) 49 | layout.addLayout(password_layout) 50 | 51 | # Buttons 52 | buttons_layout = QHBoxLayout() 53 | self.cancel_btn = QPushButton("Annulla") 54 | self.confirm_btn = QPushButton("Salva") 55 | 56 | self.cancel_btn.clicked.connect(self.reject) 57 | self.confirm_btn.clicked.connect(self.accept) 58 | 59 | buttons_layout.addWidget(self.cancel_btn) 60 | buttons_layout.addWidget(self.confirm_btn) 61 | layout.addLayout(buttons_layout) 62 | 63 | self.setLayout(layout) 64 | 65 | # If in edit mode, disable service field 66 | if edit_mode: 67 | self.service_input.setReadOnly(True) 68 | self.service_input.setStyleSheet("background-color: #444;") 69 | 70 | def toggle_password_visibility(self): 71 | if self.password_input.echoMode() == QLineEdit.Password: 72 | self.password_input.setEchoMode(QLineEdit.Normal) 73 | self.show_password_btn.setText("🔒") 74 | else: 75 | self.password_input.setEchoMode(QLineEdit.Password) 76 | self.show_password_btn.setText("👁️") 77 | 78 | def get_values(self): 79 | return (self.service_input.text(), 80 | self.username_input.text(), 81 | self.password_input.text()) 82 | 83 | class InitialKeyDialog(QDialog): 84 | def __init__(self, parent=None): 85 | super().__init__(parent) 86 | self.setWindowTitle("Configurazione Chiave") 87 | self.setMinimumWidth(400) 88 | 89 | layout = QVBoxLayout() 90 | 91 | # Title 92 | title_label = QLabel("Benvenuto in Cifra") 93 | title_label.setFont(QFont("Arial", 14, QFont.Bold)) 94 | title_label.setAlignment(Qt.AlignCenter) 95 | layout.addWidget(title_label) 96 | 97 | # Description 98 | desc_label = QLabel("Per iniziare, hai bisogno di una chiave segreta per criptare le tue password.") 99 | desc_label.setWordWrap(True) 100 | layout.addWidget(desc_label) 101 | 102 | # Buttons 103 | self.new_key_btn = QPushButton("Genera Nuova Chiave") 104 | self.existing_key_btn = QPushButton("Carica Chiave Esistente") 105 | 106 | self.new_key_btn.clicked.connect(self.accept_new) 107 | self.existing_key_btn.clicked.connect(self.accept_existing) 108 | 109 | layout.addWidget(self.new_key_btn) 110 | layout.addWidget(self.existing_key_btn) 111 | 112 | self.setLayout(layout) 113 | self.choice = None 114 | 115 | def accept_new(self): 116 | self.choice = "new" 117 | self.accept() 118 | 119 | def accept_existing(self): 120 | self.choice = "existing" 121 | self.accept() 122 | 123 | class PasswordManager(QMainWindow): 124 | def __init__(self): 125 | super().__init__() 126 | 127 | self.passwords = [] 128 | self.key = None 129 | 130 | self.setup_ui() 131 | self.init_key() 132 | 133 | def setup_ui(self): 134 | self.setWindowTitle("Cifra - Gestore Password") 135 | self.setMinimumSize(800, 500) 136 | 137 | # Main widget and layout 138 | main_widget = QWidget() 139 | main_layout = QVBoxLayout() 140 | main_widget.setLayout(main_layout) 141 | self.setCentralWidget(main_widget) 142 | 143 | # Create tab widget 144 | tab_widget = QTabWidget() 145 | 146 | # Password list tab 147 | password_tab = QWidget() 148 | password_layout = QVBoxLayout() 149 | 150 | # Header 151 | header_layout = QHBoxLayout() 152 | title_label = QLabel("Le Tue Password") 153 | title_label.setFont(QFont("Arial", 14, QFont.Bold)) 154 | 155 | # Search field 156 | self.search_input = QLineEdit() 157 | self.search_input.setPlaceholderText("Cerca servizio...") 158 | self.search_input.textChanged.connect(self.filter_table) 159 | 160 | header_layout.addWidget(title_label) 161 | header_layout.addStretch() 162 | header_layout.addWidget(self.search_input) 163 | 164 | password_layout.addLayout(header_layout) 165 | 166 | # Password table 167 | self.password_table = QTableWidget(0, 3) 168 | self.password_table.setHorizontalHeaderLabels(["Servizio", "Username", "Password"]) 169 | self.password_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) 170 | self.password_table.setSelectionBehavior(QTableWidget.SelectRows) 171 | self.password_table.setEditTriggers(QTableWidget.NoEditTriggers) 172 | self.password_table.setAlternatingRowColors(True) 173 | self.password_table.setSortingEnabled(True) 174 | password_layout.addWidget(self.password_table) 175 | 176 | # Buttons 177 | buttons_layout = QHBoxLayout() 178 | 179 | self.add_btn = QPushButton("Aggiungi") 180 | self.edit_btn = QPushButton("Modifica") 181 | self.delete_btn = QPushButton("Elimina") 182 | self.view_btn = QPushButton("Mostra Password") 183 | 184 | self.add_btn.clicked.connect(self.add_password) 185 | self.edit_btn.clicked.connect(self.edit_password) 186 | self.delete_btn.clicked.connect(self.delete_password) 187 | self.view_btn.clicked.connect(self.view_password) 188 | 189 | buttons_layout.addWidget(self.add_btn) 190 | buttons_layout.addWidget(self.edit_btn) 191 | buttons_layout.addWidget(self.delete_btn) 192 | buttons_layout.addWidget(self.view_btn) 193 | 194 | password_layout.addLayout(buttons_layout) 195 | password_tab.setLayout(password_layout) 196 | 197 | # Settings tab 198 | settings_tab = QWidget() 199 | settings_layout = QVBoxLayout() 200 | 201 | # Key info 202 | key_group_layout = QVBoxLayout() 203 | key_title = QLabel("Chiave di Crittografia") 204 | key_title.setFont(QFont("Arial", 12, QFont.Bold)) 205 | 206 | key_group_layout.addWidget(key_title) 207 | 208 | key_info_layout = QHBoxLayout() 209 | key_label = QLabel("File chiave corrente:") 210 | self.key_path_label = QLabel("Nessuna chiave caricata") 211 | key_info_layout.addWidget(key_label) 212 | key_info_layout.addWidget(self.key_path_label) 213 | key_info_layout.addStretch() 214 | 215 | key_group_layout.addLayout(key_info_layout) 216 | 217 | key_buttons_layout = QHBoxLayout() 218 | self.gen_key_btn = QPushButton("Genera Nuova Chiave") 219 | self.load_key_btn = QPushButton("Carica Chiave Esistente") 220 | self.save_key_btn = QPushButton("Esporta Chiave") 221 | 222 | self.gen_key_btn.clicked.connect(self.generate_key) 223 | self.load_key_btn.clicked.connect(self.load_key) 224 | self.save_key_btn.clicked.connect(self.save_key) 225 | 226 | key_buttons_layout.addWidget(self.gen_key_btn) 227 | key_buttons_layout.addWidget(self.load_key_btn) 228 | key_buttons_layout.addWidget(self.save_key_btn) 229 | 230 | key_group_layout.addLayout(key_buttons_layout) 231 | settings_layout.addLayout(key_group_layout) 232 | 233 | # Warning note 234 | warning_frame = QFrame() 235 | warning_frame.setFrameShape(QFrame.StyledPanel) 236 | warning_frame.setStyleSheet("QFrame { background-color: #FFA500; border-radius: 5px; }") 237 | warning_layout = QVBoxLayout(warning_frame) 238 | 239 | warning_title = QLabel("⚠️ Attenzione") 240 | warning_title.setFont(QFont("Arial", 12, QFont.Bold)) 241 | warning_text = QLabel( 242 | "La chiave segreta è essenziale per la sicurezza delle tue password.\n" 243 | "• Conservala in un luogo sicuro\n" 244 | "• Se perdi la chiave, non potrai più accedere alle tue password\n" 245 | "• Non generare più chiavi se non necessario" 246 | ) 247 | warning_text.setWordWrap(True) 248 | 249 | warning_layout.addWidget(warning_title) 250 | warning_layout.addWidget(warning_text) 251 | settings_layout.addWidget(warning_frame) 252 | 253 | # About section 254 | about_layout = QVBoxLayout() 255 | about_title = QLabel("Informazioni") 256 | about_title.setFont(QFont("Arial", 12, QFont.Bold)) 257 | 258 | about_text = QLabel( 259 | "Cifra - Gestore Password\n" 260 | "Un semplice gestore di password per mantenere al sicuro le tue credenziali.\n" 261 | "Versione 2.0 - GUI Edition" 262 | ) 263 | about_text.setWordWrap(True) 264 | about_text.setAlignment(Qt.AlignCenter) 265 | 266 | about_layout.addWidget(about_title) 267 | about_layout.addWidget(about_text) 268 | settings_layout.addLayout(about_layout) 269 | 270 | # Add spacer to push everything up 271 | settings_layout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) 272 | 273 | settings_tab.setLayout(settings_layout) 274 | 275 | # Add tabs to widget 276 | tab_widget.addTab(password_tab, "Password") 277 | tab_widget.addTab(settings_tab, "Impostazioni") 278 | 279 | main_layout.addWidget(tab_widget) 280 | 281 | def init_key(self): 282 | dialog = InitialKeyDialog(self) 283 | result = dialog.exec_() 284 | 285 | if result == QDialog.Accepted: 286 | if dialog.choice == "new": 287 | self.generate_key() 288 | elif dialog.choice == "existing": 289 | if not self.load_key(): 290 | # If loading failed, ask again 291 | self.init_key() 292 | else: 293 | # User closed the dialog without choosing 294 | sys.exit() 295 | 296 | def generate_key(self): 297 | key = Fernet.generate_key() 298 | 299 | # Ask where to save the key 300 | file_path, _ = QFileDialog.getSaveFileName( 301 | self, "Salva Chiave Segreta", "secret.key", "Key Files (*.key)" 302 | ) 303 | 304 | if file_path: 305 | try: 306 | with open(file_path, "wb") as key_file: 307 | key_file.write(key) 308 | 309 | self.key = key 310 | self.key_path_label.setText(file_path) 311 | 312 | QMessageBox.information( 313 | self, 314 | "Chiave Generata", 315 | f"La chiave è stata generata e salvata in:\n{file_path}\n\n" 316 | "IMPORTANTE: Conserva questa chiave in un luogo sicuro. " 317 | "Senza di essa non potrai recuperare le tue password!" 318 | ) 319 | 320 | # Load empty passwords list 321 | self.passwords = [] 322 | self.update_table() 323 | 324 | return True 325 | except Exception as e: 326 | QMessageBox.critical(self, "Errore", f"Impossibile salvare la chiave: {str(e)}") 327 | return False 328 | 329 | return False 330 | 331 | def save_key(self): 332 | if not self.key: 333 | QMessageBox.warning(self, "Attenzione", "Nessuna chiave caricata da esportare.") 334 | return 335 | 336 | file_path, _ = QFileDialog.getSaveFileName( 337 | self, "Esporta Chiave Segreta", "secret.key", "Key Files (*.key)" 338 | ) 339 | 340 | if file_path: 341 | try: 342 | with open(file_path, "wb") as key_file: 343 | key_file.write(self.key) 344 | 345 | QMessageBox.information( 346 | self, 347 | "Chiave Esportata", 348 | f"La chiave è stata esportata in:\n{file_path}" 349 | ) 350 | except Exception as e: 351 | QMessageBox.critical(self, "Errore", f"Impossibile esportare la chiave: {str(e)}") 352 | 353 | def load_key(self): 354 | file_path, _ = QFileDialog.getOpenFileName( 355 | self, "Seleziona File Chiave", "", "Key Files (*.key);;All Files (*)" 356 | ) 357 | 358 | if file_path: 359 | try: 360 | with open(file_path, "rb") as key_file: 361 | key = key_file.read() 362 | 363 | self.key = key 364 | self.key_path_label.setText(file_path) 365 | 366 | # Load passwords with the new key 367 | self.load_passwords() 368 | 369 | return True 370 | except Exception as e: 371 | QMessageBox.critical(self, "Errore", f"Impossibile caricare la chiave: {str(e)}") 372 | return False 373 | 374 | return False 375 | 376 | def encrypt_password(self, text): 377 | if not self.key: 378 | QMessageBox.critical(self, "Errore", "Nessuna chiave caricata.") 379 | return None 380 | 381 | fernet = Fernet(self.key) 382 | encrypted = fernet.encrypt(text.encode()) 383 | return encrypted 384 | 385 | def decrypt_password(self, encrypted_text): 386 | if not self.key: 387 | QMessageBox.critical(self, "Errore", "Nessuna chiave caricata.") 388 | return None 389 | 390 | try: 391 | fernet = Fernet(self.key) 392 | decrypted = fernet.decrypt(encrypted_text).decode() 393 | return decrypted 394 | except Exception as e: 395 | QMessageBox.critical( 396 | self, 397 | "Errore di decrittazione", 398 | "Impossibile decifrare i dati. La chiave potrebbe essere errata." 399 | ) 400 | return None 401 | 402 | def save_passwords(self): 403 | if not self.key: 404 | QMessageBox.critical(self, "Errore", "Nessuna chiave caricata.") 405 | return False 406 | 407 | try: 408 | encrypted_data = [] 409 | for service, username, password in self.passwords: 410 | encrypted_service = self.encrypt_password(service) 411 | encrypted_username = self.encrypt_password(username) 412 | encrypted_password = self.encrypt_password(password) 413 | encrypted_data.append((encrypted_service, encrypted_username, encrypted_password)) 414 | 415 | with open("passwords.dat", "wb") as password_file: 416 | pickle.dump(encrypted_data, password_file) 417 | 418 | return True 419 | except Exception as e: 420 | QMessageBox.critical(self, "Errore", f"Impossibile salvare le password: {str(e)}") 421 | return False 422 | 423 | def load_passwords(self): 424 | self.passwords = [] 425 | 426 | if not self.key: 427 | QMessageBox.critical(self, "Errore", "Nessuna chiave caricata.") 428 | return False 429 | 430 | if os.path.exists("passwords.dat"): 431 | try: 432 | with open("passwords.dat", "rb") as password_file: 433 | encrypted_data = pickle.load(password_file) 434 | 435 | for encrypted_service, encrypted_username, encrypted_password in encrypted_data: 436 | service = self.decrypt_password(encrypted_service) 437 | username = self.decrypt_password(encrypted_username) 438 | password = self.decrypt_password(encrypted_password) 439 | 440 | if service and username and password: 441 | self.passwords.append((service, username, password)) 442 | 443 | self.update_table() 444 | return True 445 | except Exception as e: 446 | QMessageBox.critical( 447 | self, 448 | "Errore", 449 | f"Impossibile caricare le password: {str(e)}\n" 450 | "La chiave potrebbe essere errata." 451 | ) 452 | return False 453 | else: 454 | self.update_table() 455 | return True 456 | 457 | def update_table(self): 458 | self.password_table.setRowCount(0) 459 | 460 | if not self.passwords: 461 | return 462 | 463 | for row, (service, username, _) in enumerate(self.passwords): 464 | self.password_table.insertRow(row) 465 | 466 | service_item = QTableWidgetItem(service) 467 | username_item = QTableWidgetItem(username) 468 | password_item = QTableWidgetItem("••••••••") 469 | 470 | self.password_table.setItem(row, 0, service_item) 471 | self.password_table.setItem(row, 1, username_item) 472 | self.password_table.setItem(row, 2, password_item) 473 | 474 | def filter_table(self): 475 | search_text = self.search_input.text().lower() 476 | 477 | for row in range(self.password_table.rowCount()): 478 | service = self.password_table.item(row, 0).text().lower() 479 | username = self.password_table.item(row, 1).text().lower() 480 | 481 | if search_text in service or search_text in username: 482 | self.password_table.setRowHidden(row, False) 483 | else: 484 | self.password_table.setRowHidden(row, True) 485 | 486 | def add_password(self): 487 | if not self.key: 488 | QMessageBox.warning(self, "Attenzione", "Carica o genera una chiave prima di aggiungere password.") 489 | return 490 | 491 | dialog = PasswordEntry(self) 492 | result = dialog.exec_() 493 | 494 | if result == QDialog.Accepted: 495 | service, username, password = dialog.get_values() 496 | 497 | if not service or not username or not password: 498 | QMessageBox.warning(self, "Campi vuoti", "Tutti i campi sono obbligatori.") 499 | return 500 | 501 | # Check if service already exists 502 | for s, _, _ in self.passwords: 503 | if s == service: 504 | QMessageBox.warning( 505 | self, 506 | "Servizio duplicato", 507 | f"Esiste già un servizio chiamato '{service}'." 508 | ) 509 | return 510 | 511 | self.passwords.append((service, username, password)) 512 | self.save_passwords() 513 | self.update_table() 514 | 515 | QMessageBox.information(self, "Successo", "Password salvata con successo!") 516 | 517 | def edit_password(self): 518 | if not self.password_table.selectedItems(): 519 | QMessageBox.warning(self, "Nessuna selezione", "Seleziona un servizio da modificare.") 520 | return 521 | 522 | selected_row = self.password_table.currentRow() 523 | service, username, password = self.passwords[selected_row] 524 | 525 | dialog = PasswordEntry(self, service, username, edit_mode=True) 526 | result = dialog.exec_() 527 | 528 | if result == QDialog.Accepted: 529 | _, new_username, new_password = dialog.get_values() 530 | 531 | if not new_username or not new_password: 532 | QMessageBox.warning(self, "Campi vuoti", "Tutti i campi sono obbligatori.") 533 | return 534 | 535 | self.passwords[selected_row] = (service, new_username, new_password) 536 | self.save_passwords() 537 | self.update_table() 538 | 539 | QMessageBox.information(self, "Successo", "Password aggiornata con successo!") 540 | 541 | def delete_password(self): 542 | if not self.password_table.selectedItems(): 543 | QMessageBox.warning(self, "Nessuna selezione", "Seleziona un servizio da eliminare.") 544 | return 545 | 546 | selected_row = self.password_table.currentRow() 547 | service = self.passwords[selected_row][0] 548 | 549 | confirm = QMessageBox.question( 550 | self, 551 | "Conferma eliminazione", 552 | f"Sei sicuro di voler eliminare il servizio '{service}'?", 553 | QMessageBox.Yes | QMessageBox.No, 554 | QMessageBox.No 555 | ) 556 | 557 | if confirm == QMessageBox.Yes: 558 | del self.passwords[selected_row] 559 | self.save_passwords() 560 | self.update_table() 561 | 562 | QMessageBox.information(self, "Successo", "Servizio eliminato con successo!") 563 | 564 | def view_password(self): 565 | if not self.password_table.selectedItems(): 566 | QMessageBox.warning(self, "Nessuna selezione", "Seleziona un servizio per visualizzare la password.") 567 | return 568 | 569 | selected_row = self.password_table.currentRow() 570 | service, username, password = self.passwords[selected_row] 571 | 572 | msg = QMessageBox(self) 573 | msg.setWindowTitle("Dettagli Password") 574 | msg.setText(f"Servizio: {service}
" 575 | f"Username: {username}
" 576 | f"Password: {password}") 577 | msg.setStandardButtons(QMessageBox.Ok) 578 | msg.exec_() 579 | 580 | if __name__ == "__main__": 581 | app = QApplication(sys.argv) 582 | app.setStyle("Fusion") 583 | app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) 584 | 585 | window = PasswordManager() 586 | window.show() 587 | 588 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔒 Simple Password Manager 2 | 3 | ![Python](https://img.shields.io/badge/-Python-black?style=flat-square&logo=Python) 4 | 5 | A simple password manager capable of securely storing your passwords. 🗝️ 6 | 7 | ## 📝 Instructions 8 | 9 | ### 📚 Prerequisites 10 | - Install Python from the [official website](https://www.python.org/) (latest version) and ensure it is added to the system Path and environment variables. 11 | 12 | ### 📥 Installation 13 | 1. Open a terminal or powershell/command prompt. 🖥️ 14 | 2. Launch the following command to install the required dependencies: 15 | ``` 16 | pip install -r requirements.txt 17 | ``` 18 | 19 | ### 🚀 Usage 20 | 1. Navigate to the script folder in the terminal or powershell/command prompt. 21 | 2. Launch the script with Python by executing: 22 | ``` 23 | python Cifra.py 24 | ``` 25 | 26 | ## 📌 Notes 27 | - **Caution**: Avoid generating more than one secret key. By default, it is generated in the current folder. If you generate a new key without removing the old one, it will be overwritten. ⚠️ 28 | - This program is intended for educational use only. 🎓 29 | - Feel free to contribute and improve the program. For example, there is currently no functionality to change passwords already saved. 🛠️ 30 | 31 | ## 🔄 Compatibility 32 | - This program is compatible with Windows, macOS, and GNU/Linux operating systems. 💻 33 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cryptography 2 | colorama --------------------------------------------------------------------------------