├── 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 | 
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
--------------------------------------------------------------------------------