11import threading
22import tkinter as tk
3- from tkinter import filedialog , messagebox , scrolledtext
3+ from tkinter import filedialog , messagebox , scrolledtext , simpledialog
44import ttkbootstrap as tb
55from cryptography .fernet import Fernet
6+ from cryptography .hazmat .primitives .kdf .pbkdf2 import PBKDF2HMAC
7+ from cryptography .hazmat .primitives import hashes
8+ from cryptography .hazmat .backends import default_backend
69import os
710import sys
811import base64
9- import hashlib
1012
11- def resource_path (file_name ):
12- base_path = getattr (sys , '_MEIPASS' , os .path .dirname (os .path .abspath (__file__ )))
13- return os .path .join (base_path , file_name )
14-
15- def derive_key (password : str ) -> bytes :
16- """Derive a Fernet key from a password using SHA256"""
17- return base64 .urlsafe_b64encode (hashlib .sha256 (password .encode ()).digest ())
13+ # ===== Secure Key Derivation =====
14+ def derive_key (password : str , salt : bytes ) -> bytes :
15+ """Derive a Fernet key from a password using PBKDF2HMAC and salt."""
16+ kdf = PBKDF2HMAC (
17+ algorithm = hashes .SHA256 (),
18+ length = 32 ,
19+ salt = salt ,
20+ iterations = 390_000 ,
21+ backend = default_backend ()
22+ )
23+ return base64 .urlsafe_b64encode (kdf .derive (password .encode ()))
1824
1925class FileEncryptor :
2026 def __init__ (self , root ):
2127 self .root = root
22- self .root .title ("FileCryptor - File Encryption Tool " )
28+ self .root .title ("FileCryptor - Secure File Encryption" )
2329 self .root .geometry ("1100x660" )
24- # self.root.iconbitmap(resource_path("logo.ico"))
2530
26- # ===== Buttons Frame =====
31+ # ===== Buttons =====
2732 button_frame = tb .Frame (self .root )
2833 button_frame .pack (pady = 10 )
2934
30- self .encrypt_btn = tb .Button (
31- button_frame , text = "🔒 Encrypt Files" , bootstyle = "success-outline" , width = 25 ,
32- command = lambda : self .start_process (encrypt = True )
33- )
35+ self .encrypt_btn = tb .Button (button_frame , text = "🔒 Encrypt Files" , bootstyle = "success-outline" , width = 25 ,
36+ command = lambda : self .start_process (encrypt = True ))
3437 self .encrypt_btn .grid (row = 0 , column = 0 , padx = 5 )
3538
36- self .decrypt_btn = tb .Button (
37- button_frame , text = "🔓 Decrypt Files" , bootstyle = "info-outline" , width = 25 ,
38- command = lambda : self .start_process (encrypt = False )
39- )
39+ self .decrypt_btn = tb .Button (button_frame , text = "🔓 Decrypt Files" , bootstyle = "info-outline" , width = 25 ,
40+ command = lambda : self .start_process (encrypt = False ))
4041 self .decrypt_btn .grid (row = 0 , column = 1 , padx = 5 )
4142
4243 self .cancel_event = threading .Event ()
43-
44- self .cancel_btn = tb .Button (
45- button_frame , text = "❌ Cancel" , bootstyle = "danger-outline" , width = 20 ,
46- state = tk .DISABLED , command = self .cancel_process
47- )
44+ self .cancel_btn = tb .Button (button_frame , text = "❌ Cancel" , bootstyle = "danger-outline" , width = 20 ,
45+ state = tk .DISABLED , command = self .cancel_process )
4846 self .cancel_btn .grid (row = 0 , column = 2 , padx = 5 )
4947
50- self .clear_btn = tb .Button (
51- button_frame , text = "🧹 Clear Log" , bootstyle = "warning-outline" , width = 20 ,
52- command = self .clear_log
53- )
48+ self .clear_btn = tb .Button (button_frame , text = "🧹 Clear Log" , bootstyle = "warning-outline" , width = 20 ,
49+ command = self .clear_log )
5450 self .clear_btn .grid (row = 0 , column = 3 , padx = 5 )
5551
56- self .about_btn = tb .Button (
57- button_frame , text = "ℹ️ About" , bootstyle = "secondary-outline" , width = 20 ,
58- command = self .show_about_guide
59- )
52+ self .about_btn = tb .Button (button_frame , text = "ℹ️ About" , bootstyle = "secondary-outline" , width = 20 ,
53+ command = self .show_about_guide )
6054 self .about_btn .grid (row = 0 , column = 4 , padx = 5 )
6155
62- # ===== Log Area =====
56+ # ===== Log area =====
6357 self .log_area = scrolledtext .ScrolledText (self .root , wrap = tk .WORD , font = ("Arial" , 12 ))
6458 self .log_area .pack (expand = True , fill = tk .BOTH , padx = 10 , pady = 10 )
6559
66- # ===== Progress Bar Frame =====
60+ # ===== Progress =====
6761 progress_frame = tb .Frame (self .root )
6862 progress_frame .pack (fill = tk .X , padx = 10 , pady = (0 , 10 ))
69-
7063 self .progress_label = tb .Label (progress_frame , text = "Ready" )
7164 self .progress_label .pack (side = tk .LEFT , padx = (0 , 10 ))
72-
7365 self .progress = tb .Progressbar (progress_frame , bootstyle = "success-striped" , mode = "determinate" )
7466 self .progress .pack (side = tk .RIGHT , fill = tk .X , expand = True )
7567
68+ # ===== Logging =====
69+ def _log (self , msg ):
70+ self .log_area .insert (tk .END , msg + "\n " )
71+ self .log_area .see (tk .END )
72+
73+ def clear_log (self ):
74+ self .log_area .delete (1.0 , tk .END )
75+
76+ # ===== Cancel =====
7677 def cancel_process (self ):
7778 self .cancel_event .set ()
7879 self .progress_label .config (text = "Cancelling..." )
7980
81+ # ===== UI Reset =====
8082 def _reset_ui (self ):
8183 self .progress ["value" ] = 0
8284 self .progress_label .config (text = "Ready" )
@@ -85,73 +87,60 @@ def _reset_ui(self):
8587 self .cancel_btn .config (state = tk .DISABLED )
8688 self .cancel_event .clear ()
8789
88- def _update_progress (self , value ):
89- self .progress ["value" ] = value
90- self .progress_label .config (text = f"Processing... { int (value )} %" )
91-
92- def _log (self , message ):
93- self .log_area .insert (tk .END , message + "\n " )
94- self .log_area .see (tk .END )
95-
96- def clear_log (self ):
97- self .log_area .delete (1.0 , tk .END )
98-
9990 # ===== Worker =====
10091 def _process_worker (self , file_paths , password , encrypt = True ):
101- try :
102- key = derive_key (password )
103- fernet = Fernet (key )
104- total_files = len (file_paths )
105- step = 100 / total_files
106- current_progress = 0
107-
108- for file_path in file_paths :
109- if self .cancel_event .is_set ():
110- self .root .after (0 , self ._reset_ui )
111- self ._log ("Process cancelled." )
112- return
113-
114- filename = os .path .basename (file_path )
115- try :
116- with open (file_path , "rb" ) as f :
117- data = f .read ()
118-
119- if encrypt :
120- processed_data = fernet .encrypt (data )
121- out_file = file_path + ".enc"
122- else :
123- processed_data = fernet .decrypt (data )
124- if file_path .endswith (".enc" ):
125- out_file = file_path [:- 4 ]
126- else :
127- out_file = file_path + ".dec"
128-
92+ total_files = len (file_paths )
93+ step = 100 / total_files
94+ current_progress = 0
95+
96+ for file_path in file_paths :
97+ if self .cancel_event .is_set ():
98+ self .root .after (0 , self ._reset_ui )
99+ self ._log ("Process cancelled." )
100+ return
101+
102+ filename = os .path .basename (file_path )
103+ try :
104+ with open (file_path , "rb" ) as f :
105+ data = f .read ()
106+
107+ if encrypt :
108+ salt = os .urandom (16 )
109+ key = derive_key (password , salt )
110+ fernet = Fernet (key )
111+ encrypted = fernet .encrypt (data )
112+ out_file = file_path + ".enc"
129113 with open (out_file , "wb" ) as f :
130- f .write (processed_data )
131-
132- self ._log (f"{ 'Encrypted' if encrypt else 'Decrypted' } : { filename } " )
114+ f .write (salt + encrypted ) # prepend salt
115+ else :
116+ salt = data [:16 ]
117+ encrypted_data = data [16 :]
118+ key = derive_key (password , salt )
119+ fernet = Fernet (key )
120+ decrypted = fernet .decrypt (encrypted_data )
121+ out_file = file_path [:- 4 ] if file_path .endswith (".enc" ) else file_path + ".dec"
122+ with open (out_file , "wb" ) as f :
123+ f .write (decrypted )
133124
134- except Exception as e :
135- self ._log (f"Error processing { filename } : { str (e )} " )
125+ self ._log (f"{ 'Encrypted' if encrypt else 'Decrypted' } : { filename } " )
136126
137- current_progress += step
138- self .root . after ( 0 , self . _update_progress , current_progress )
127+ except Exception as e :
128+ self ._log ( f"Error processing { filename } : { str ( e ) } " )
139129
140- self . root . after ( 0 , self . _reset_ui )
141- self ._log ( "Process completed." )
130+ current_progress += step
131+ self .root . after ( 0 , self . progress . step , step )
142132
143- except Exception as e :
144- self .root .after (0 , self ._reset_ui )
145- self ._log (f"Fatal error: { str (e )} " )
133+ self .root .after (0 , self ._reset_ui )
134+ self ._log ("Process completed." )
146135
147- # ===== Start Process =====
136+ # ===== Start =====
148137 def start_process (self , encrypt = True ):
149138 action = "Encrypt" if encrypt else "Decrypt"
150139 file_paths = filedialog .askopenfilenames (title = f"Select files to { action } " )
151140 if not file_paths :
152141 return
153142
154- password = tk . simpledialog .askstring ("Password" , f"Enter password to { action } files:" , show = "*" )
143+ password = simpledialog .askstring ("Password" , f"Enter password to { action } files:" , show = "*" )
155144 if not password :
156145 messagebox .showwarning ("Password required" , "Operation cancelled. Password is required!" )
157146 return
@@ -165,46 +154,33 @@ def start_process(self, encrypt=True):
165154
166155 threading .Thread (target = self ._process_worker , args = (file_paths , password , encrypt ), daemon = True ).start ()
167156
168- # ===== About / Guide =====
157+ # ===== About =====
169158 def show_about_guide (self ):
170159 guide_window = tb .Toplevel (self .root )
171160 guide_window .title ("📘 About / Guide" )
172161 guide_window .geometry ("600x480" )
173162 guide_window .resizable (False , False )
174163 guide_window .grab_set ()
175- guide_window .attributes ("-toolwindow" , True )
176-
177- self .root .update_idletasks ()
178- root_x = self .root .winfo_x ()
179- root_y = self .root .winfo_y ()
180- root_width = self .root .winfo_width ()
181- root_height = self .root .winfo_height ()
182- win_width = 600
183- win_height = 480
184- pos_x = root_x + (root_width // 2 ) - (win_width // 2 )
185- pos_y = root_y + (root_height // 2 ) - (win_height // 2 )
186- guide_window .geometry (f"{ win_width } x{ win_height } +{ pos_x } +{ pos_y } " )
187164
188165 frame = tb .Frame (guide_window , padding = 10 )
189166 frame .pack (fill = "both" , expand = True )
190167
191168 sections = {
192169 "About FileCryptor" : (
193170 "FileCryptor is a secure and easy-to-use desktop tool for encrypting and decrypting files.\n "
194- "It uses password-based encryption (Fernet symmetric encryption ) to protect your data."
171+ "It uses password-based encryption (PBKDF2 + Fernet ) to protect your data."
195172 ),
196173 "Key Features" : (
197174 "- Encrypt and decrypt multiple files\n "
198- "- Password protected\n "
175+ "- Password protected with secure KDF \n "
199176 "- Cancel operations anytime\n "
200177 "- Real-time progress bar\n "
201178 "- Modern, clean interface"
202179 ),
203- "Built With" : (
204- "- Python\n "
205- "- Tkinter & ttkbootstrap\n "
206- "- cryptography library\n "
207- "- Multithreading for responsive UI"
180+ "Use Case Example" : (
181+ "- Encrypt your personal documents folder before uploading to cloud storage\n "
182+ "- Decrypt project files shared by a colleague\n "
183+ "- Securely archive sensitive PDFs or images"
208184 ),
209185 "Developer" : (
210186 "Developed by MateTools\n "
0 commit comments