1818import os
1919import sys
2020import subprocess
21+ import re
22+
23+
24+ class PasswordComplexityChecker :
25+ """Checks password complexity and provides a strength score."""
26+ @staticmethod
27+ def check_password_strength (password ):
28+ """Check password complexity and return strength score (0-100)."""
29+ if not password :
30+ return 0
31+
32+ score = 0
33+
34+ # Length (max 30 points)
35+ length_score = min (len (password ) * 2 , 30 )
36+ score += length_score
37+
38+ # Character variety (max 70 points)
39+ variety_bonus = 0
40+
41+ # Uppercase letters
42+ if re .search (r"[A-Z]" , password ):
43+ variety_bonus += 10
44+
45+ # Lowercase letters
46+ if re .search (r"[a-z]" , password ):
47+ variety_bonus += 10
48+
49+ # Numbers
50+ if re .search (r"[0-9]" , password ):
51+ variety_bonus += 15
52+
53+ # Special characters
54+ if re .search (r"[^A-Za-z0-9]" , password ):
55+ variety_bonus += 20
56+
57+ # Additional bonus for multiple character types
58+ char_types = 0
59+ if re .search (r"[A-Z]" , password ):
60+ char_types += 1
61+ if re .search (r"[a-z]" , password ):
62+ char_types += 1
63+ if re .search (r"[0-9]" , password ):
64+ char_types += 1
65+ if re .search (r"[^A-Za-z0-9]" , password ):
66+ char_types += 1
67+
68+ if char_types >= 3 :
69+ variety_bonus += 15
70+ elif char_types >= 2 :
71+ variety_bonus += 5
72+
73+ score += min (variety_bonus , 70 )
74+
75+ return min (score , 100 )
2176
2277
2378class PlaceholderLineEdit (QLineEdit ):
@@ -30,7 +85,7 @@ class PlaceholderLineEdit(QLineEdit):
3085 user_interaction (bool): Tracks if user has interacted with the field
3186 user_input (bool): Tracks if user has entered any text
3287 """
33- def __init__ (self , placeholder = '' , color = ' gray' , parent = None ):
88+ def __init__ (self , placeholder = "" , color = " gray" , parent = None ):
3489 super ().__init__ (parent )
3590 self .placeholder = placeholder
3691 self .placeholder_color = QColor (color )
@@ -95,6 +150,30 @@ def __init__(self, text, parent=None):
95150 self .setStyleSheet (UITheme .get_styled_button_style ())
96151
97152
153+ class PasswordStrengthBar (QProgressBar ):
154+ """A progress bar that shows password strength with color coding."""
155+ def __init__ (self , parent = None ):
156+ super ().__init__ (parent )
157+ self .setFixedHeight (6 )
158+ self .setTextVisible (False )
159+ self .setRange (0 , 100 )
160+ self .setValue (0 )
161+ self .update_style (0 )
162+
163+ def update_strength (self , password ):
164+ """Update the strength bar based on password."""
165+ if not password : # Empty password
166+ score = 0
167+ else :
168+ score = PasswordComplexityChecker .check_password_strength (password )
169+ self .setValue (score )
170+ self .update_style (score )
171+
172+ def update_style (self , score ):
173+ """Update the style based on password strength score."""
174+ self .setStyleSheet (UITheme .get_password_strength_bar_style (score ))
175+
176+
98177class MainWindow (QMainWindow ):
99178 """The main application window for file encryption/decryption.
100179
@@ -113,7 +192,7 @@ def __init__(self, file_path=None):
113192 super ().__init__ ()
114193 self .setAcceptDrops (True )
115194 self .setWindowTitle (f"{ APP_NAME } { CURRENT_VERSION } " )
116- self .setFixedSize (450 , 460 )
195+ self .setFixedSize (450 , 472 )
117196 self .setWindowOpacity (0.95 )
118197
119198 # Create application icon
@@ -129,7 +208,7 @@ def __init__(self, file_path=None):
129208
130209 # Create main container
131210 main_frame = QFrame (self )
132- main_frame .setGeometry (10 , 10 , 430 , 444 )
211+ main_frame .setGeometry (10 , 10 , 430 , 459 )
133212
134213 # Layout positions
135214 y_pos = 0
@@ -139,7 +218,7 @@ def __init__(self, file_path=None):
139218 file_section_label .setGeometry (0 , y_pos , 430 , 20 )
140219 y_pos += 25
141220
142- self .file_path_entry = PlaceholderLineEdit (placeholder = "File/Folder Path" , color = ' #888' , parent = main_frame )
221+ self .file_path_entry = PlaceholderLineEdit (placeholder = "File/Folder Path" , color = " #888" , parent = main_frame )
143222 self .file_path_entry .setGeometry (0 , y_pos , 430 , 24 )
144223 self .file_path_entry .setText (os .getcwd ())
145224 y_pos += 30
@@ -166,28 +245,52 @@ def __init__(self, file_path=None):
166245 security_section_label .setGeometry (0 , y_pos , 430 , 20 )
167246 y_pos += 25
168247
169- self .password_entry = PlaceholderLineEdit (placeholder = "Password" , color = ' #888' , parent = main_frame )
248+ self .password_entry = PlaceholderLineEdit (placeholder = "Password" , color = " #888" , parent = main_frame )
170249 self .password_entry .setGeometry (0 , y_pos , 171 , 24 )
171250 self .password_entry .setEchoMode (QLineEdit .EchoMode .Password )
251+ self .password_entry .textChanged .connect (self .update_password_strength )
172252
173- self .confirm_password_entry = PlaceholderLineEdit (placeholder = "Confirm Password" , color = ' #888' , parent = main_frame )
253+ self .confirm_password_entry = PlaceholderLineEdit (placeholder = "Confirm Password" , color = " #888" , parent = main_frame )
174254 self .confirm_password_entry .setGeometry (177 , y_pos , 172 , 24 )
175255 self .confirm_password_entry .setEchoMode (QLineEdit .EchoMode .Password )
256+ self .confirm_password_entry .textChanged .connect (self .check_password_match )
176257
177258 self .password_show_button = StyledButton ("Show \U0001F513 " , main_frame ) # \U0001F513 = 🔓
178259 self .password_show_button .setGeometry (355 , y_pos , 75 , 24 )
179260 self .password_show_button .clicked .connect (self .toggle_password_visibility )
180261 y_pos += 30
181262
182- self .recovery_key_entry = PlaceholderLineEdit (placeholder = "Recovery key file" , color = '#888' , parent = main_frame )
263+ # Password strength bar (simple progress bar style)
264+ self .password_strength_bar = PasswordStrengthBar (main_frame )
265+ self .password_strength_bar .setGeometry (0 , y_pos , 430 , 6 )
266+ y_pos += 8
267+
268+ # Password match indicator
269+ self .password_match_label = QLabel (main_frame )
270+ self .password_match_label .setGeometry (0 , y_pos , 430 , 16 )
271+ self .password_match_label .setStyleSheet (
272+ """
273+ QLabel {
274+ font-size: 11px;
275+ color: #888;
276+ background-color: transparent;
277+ padding: 0px;
278+ margin: 0px;
279+ }
280+ """
281+ )
282+ self .password_match_label .setText ("" )
283+ y_pos += 4
284+
285+ self .recovery_key_entry = PlaceholderLineEdit (placeholder = "Recovery key file" , color = "#888" , parent = main_frame )
183286 self .recovery_key_entry .setGeometry (0 , y_pos , 349 , 24 )
184287
185288 self .recovery_key_select_button = StyledButton ("Select \U0001F510 " , main_frame ) # \U0001F510 = 🔐
186289 self .recovery_key_select_button .setGeometry (355 , y_pos , 75 , 24 )
187290 self .recovery_key_select_button .clicked .connect (self .recovery_key_select_button_clicked )
188291 y_pos += 30
189292
190- self .iv_key_file_entry = PlaceholderLineEdit (placeholder = "IV-key file" , color = ' #888' , parent = main_frame )
293+ self .iv_key_file_entry = PlaceholderLineEdit (placeholder = "IV-key file" , color = " #888" , parent = main_frame )
191294 self .iv_key_file_entry .setGeometry (0 , y_pos , 349 , 24 )
192295
193296 self .iv_key_file_select_button = StyledButton ("Select \U0001F511 " , main_frame ) # \U0001F511 = 🔑
@@ -266,6 +369,61 @@ def __init__(self, file_path=None):
266369 # Apply the color palette
267370 self .set_color_palette ()
268371
372+ def update_password_strength (self ):
373+ """Update password strength bar in real-time."""
374+ password = self .password_entry .text ()
375+ self .password_strength_bar .update_strength (password )
376+
377+ # Also check password match when password changes
378+ self .check_password_match ()
379+
380+ def check_password_match (self ):
381+ """Check if passwords match and update indicator."""
382+ password = self .password_entry .text ()
383+ confirm_password = self .confirm_password_entry .text ()
384+
385+ if not password or not confirm_password :
386+ self .password_match_label .setText ("" )
387+ self .password_match_label .setStyleSheet (
388+ """
389+ QLabel {
390+ font-size: 11px;
391+ color: #888;
392+ background-color: transparent;
393+ padding: 0px;
394+ margin: 0px;
395+ }
396+ """
397+ )
398+ elif password == confirm_password :
399+ self .password_match_label .setText ("✓ Passwords match" )
400+ self .password_match_label .setStyleSheet (
401+ """
402+ QLabel {
403+ font-size: 11px;
404+ color: #007E33;
405+ background-color: transparent;
406+ font-weight: bold;
407+ padding: 0px;
408+ margin: 0px;
409+ }
410+ """
411+ )
412+ else :
413+ self .password_match_label .setText ("✗ Passwords do not match" )
414+ self .password_match_label .setStyleSheet (
415+ """
416+ QLabel {
417+ font-size: 11px;
418+ color: #ff4444;
419+ background-color: transparent;
420+ font-weight: bold;
421+ padding: 0px;
422+ margin: 0px;
423+ }
424+ """
425+ )
426+
269427 def set_color_palette (self ):
270428 """Applies the UI theme's color palette and stylesheet to the window."""
271429 self .setPalette (UITheme .get_color_palette ())
@@ -300,7 +458,6 @@ def dropEvent(self, event):
300458 else :
301459 self .file_path_entry .setText (file_path )
302460
303-
304461 def update_progress (self , value ):
305462 """Updates the progress bar with the current operation progress.
306463
@@ -371,10 +528,10 @@ def open_file_manager(self):
371528 if not os .path .exists (path ):
372529 path = os .getcwd ()
373530
374- if sys .platform .startswith (' win' ):
531+ if sys .platform .startswith (" win" ):
375532 subprocess .Popen (f'explorer "{ path } "' , shell = False )
376533 else :
377- subprocess .Popen ([' xdg-open' , path ])
534+ subprocess .Popen ([" xdg-open" , path ])
378535
379536 def select_file (self ):
380537 """Opens a file dialog and sets the selected file path."""
@@ -393,7 +550,7 @@ def toggle_password_visibility(self):
393550 if self .password_entry .echoMode () == QLineEdit .EchoMode .Password :
394551 self .password_entry .setEchoMode (QLineEdit .EchoMode .Normal )
395552 self .confirm_password_entry .setEchoMode (QLineEdit .EchoMode .Normal )
396- self .password_show_button .setText ("Hide \U0001F512 " ) # \U0001F6E1 = 🔒
553+ self .password_show_button .setText ("Hide \U0001F512 " ) # \U0001F512 = 🔒
397554 else :
398555 self .password_entry .setEchoMode (QLineEdit .EchoMode .Password )
399556 self .confirm_password_entry .setEchoMode (QLineEdit .EchoMode .Password )
@@ -542,7 +699,7 @@ def decrypt_button_click(self):
542699 separate_iv_key = iv_key_file .strip ()
543700 if not recovery_key_file .strip () == "" :
544701 hash_password = False
545- with open (recovery_key_file .strip (), 'r' ) as file :
702+ with open (recovery_key_file .strip (), "r" ) as file :
546703 password = file .read (64 )
547704
548705 # Disable UI during operation
0 commit comments