11import tkinter as tk
22from tkinter import messagebox
3- import hashlib
43import json
54import os
5+ import re
6+
7+ from argon2 import PasswordHasher
8+ from argon2 .exceptions import VerifyMismatchError
9+
10+
11+ # -------------------- Config --------------------
612
713USER_FILE = 'users.json'
814
15+ ph = PasswordHasher (
16+ time_cost = 3 , # iterations
17+ memory_cost = 65536 , # 64 MB
18+ parallelism = 4 ,
19+ hash_len = 32 ,
20+ salt_len = 16
21+ )
22+
23+
24+ # -------------------- Storage --------------------
25+
926def load_users ():
1027 if os .path .exists (USER_FILE ):
1128 with open (USER_FILE , 'r' ) as f :
1229 return json .load (f )
1330 return {}
1431
32+
1533def save_users (users ):
1634 with open (USER_FILE , 'w' ) as f :
17- json .dump (users , f )
35+ json .dump (users , f , indent = 4 )
36+
37+
38+ # -------------------- Password Security --------------------
39+
40+ def hash_password (password : str ) -> str :
41+ return ph .hash (password )
42+
43+
44+ def verify_password (password : str , stored_hash : str ) -> bool :
45+ try :
46+ return ph .verify (stored_hash , password )
47+ except VerifyMismatchError :
48+ return False
49+
1850
19- def hash_password (password ):
20- return hashlib .sha256 (password .encode ()).hexdigest ()
51+ def validate_password_strength (password : str ) -> str | None :
52+ if len (password ) < 12 :
53+ return "Password must be at least 12 characters long"
54+
55+ if not re .search (r"[A-Z]" , password ):
56+ return "Password must contain an uppercase letter"
57+
58+ if not re .search (r"[a-z]" , password ):
59+ return "Password must contain a lowercase letter"
60+
61+ if not re .search (r"\d" , password ):
62+ return "Password must contain a digit"
63+
64+ if not re .search (r"[!@#$%^&*(),.?\":{}|<>]" , password ):
65+ return "Password must contain a special character"
66+
67+ return None
68+
69+
70+ # -------------------- App --------------------
2171
2272class LoginApp :
2373 def __init__ (self , root ):
2474 self .root = root
25- self .root .title ('Modern Login System' )
75+ self .root .title ('Secure Login System' )
2676 self .root .geometry ('450x550' )
2777 self .root .configure (bg = '#1f2f3a' )
2878 self .root .resizable (False , False )
79+
2980 self .users = load_users ()
3081 self .create_login_screen ()
3182
3283 def clear_screen (self ):
3384 for widget in self .root .winfo_children ():
3485 widget .destroy ()
3586
87+ # -------------------- Login Screen --------------------
88+
3689 def create_login_screen (self ):
3790 self .clear_screen ()
38- tk .Label (self .root , text = 'Welcome Back!' , font = ('Helvetica' , 28 , 'bold' ), bg = '#1f2f3a' , fg = '#ffffff' ).pack (pady = 30 )
39-
40- # Username input
41- username_frame = tk .Frame (self .root , bg = '#1f2f3a' )
42- username_frame .pack (pady = 10 )
43- tk .Label (username_frame , text = 'Username' , font = ('Helvetica' , 12 ), bg = '#1f2f3a' , fg = '#bdc3c7' ).pack (anchor = 'w' )
44- self .username_entry = tk .Entry (username_frame , font = ('Helvetica' , 14 ), bd = 0 , highlightthickness = 2 , highlightbackground = '#2980b9' , width = 30 )
45- self .username_entry .pack (pady = 5 , ipady = 6 ) # ipady increases vertical padding inside entry
46-
47- # Password input
48- password_frame = tk .Frame (self .root , bg = '#1f2f3a' )
49- password_frame .pack (pady = 10 )
50- tk .Label (password_frame , text = 'Password' , font = ('Helvetica' , 12 ), bg = '#1f2f3a' , fg = '#bdc3c7' ).pack (anchor = 'w' )
51- self .password_entry = tk .Entry (password_frame , font = ('Helvetica' , 14 ), bd = 0 , highlightthickness = 2 , highlightbackground = '#2980b9' , show = '*' , width = 30 )
52- self .password_entry .pack (pady = 5 , ipady = 6 )
53-
54- # Buttons
55- btn_frame = tk .Frame (self .root , bg = '#1f2f3a' )
56- btn_frame .pack (pady = 20 )
57-
58- login_btn = tk .Button (btn_frame , text = 'Login' , command = self .login , bg = '#2980b9' , fg = 'white' , font = ('Helvetica' , 12 , 'bold' ), width = 25 , height = 2 , bd = 0 , activebackground = '#3498db' , cursor = 'hand2' )
59- login_btn .pack (pady = 5 )
60- register_btn = tk .Button (btn_frame , text = 'Register' , command = self .create_register_screen , bg = '#27ae60' , fg = 'white' , font = ('Helvetica' , 12 , 'bold' ), width = 25 , height = 2 , bd = 0 , activebackground = '#2ecc71' , cursor = 'hand2' )
61- register_btn .pack (pady = 5 )
62- forgot_btn = tk .Button (btn_frame , text = 'Forgot Password?' , command = self .forgot_password , bg = '#e67e22' , fg = 'white' , font = ('Helvetica' , 12 , 'bold' ), width = 25 , height = 2 , bd = 0 , activebackground = '#f39c12' , cursor = 'hand2' )
63- forgot_btn .pack (pady = 5 )
91+
92+ tk .Label (
93+ self .root ,
94+ text = 'Welcome Back!' ,
95+ font = ('Helvetica' , 28 , 'bold' ),
96+ bg = '#1f2f3a' ,
97+ fg = '#ffffff'
98+ ).pack (pady = 30 )
99+
100+ frame = tk .Frame (self .root , bg = '#1f2f3a' )
101+ frame .pack (pady = 10 )
102+
103+ tk .Label (frame , text = 'Username' , fg = '#bdc3c7' , bg = '#1f2f3a' ).pack (anchor = 'w' )
104+ self .username_entry = tk .Entry (frame , font = ('Helvetica' , 14 ), width = 30 )
105+ self .username_entry .pack (ipady = 6 )
106+
107+ frame = tk .Frame (self .root , bg = '#1f2f3a' )
108+ frame .pack (pady = 10 )
109+
110+ tk .Label (frame , text = 'Password' , fg = '#bdc3c7' , bg = '#1f2f3a' ).pack (anchor = 'w' )
111+ self .password_entry = tk .Entry (frame , font = ('Helvetica' , 14 ), show = '*' , width = 30 )
112+ self .password_entry .pack (ipady = 6 )
113+
114+ frame = tk .Frame (self .root , bg = '#1f2f3a' )
115+ frame .pack (pady = 20 )
116+
117+ tk .Button (frame , text = 'Login' , height = 2 , width = 46 , command = self .login ).pack (pady = 5 )
118+ tk .Button (frame , text = 'Register' , height = 2 , width = 46 , command = self .create_register_screen ).pack (pady = 5 )
119+ tk .Button (frame , text = 'Forgot Password?' , height = 2 , width = 46 , command = self .forgot_password ).pack (pady = 5 )
120+
121+ # -------------------- Register Screen --------------------
64122
65123 def create_register_screen (self ):
66124 self .clear_screen ()
67- tk .Label (self .root , text = 'Create Account' , font = ('Helvetica' , 28 , 'bold' ), bg = '#1f2f3a' , fg = '#ffffff' ).pack (pady = 30 )
68-
69- # Username
70- username_frame = tk .Frame (self .root , bg = '#1f2f3a' )
71- username_frame .pack (pady = 10 )
72- tk .Label (username_frame , text = 'Username' , font = ('Helvetica' , 12 ), bg = '#1f2f3a' , fg = '#bdc3c7' ).pack (anchor = 'w' )
73- self .reg_username = tk .Entry (username_frame , font = ('Helvetica' , 14 ), bd = 0 , highlightthickness = 2 , highlightbackground = '#27ae60' , width = 30 )
74- self .reg_username .pack (pady = 5 , ipady = 6 )
75-
76- # Password
77- password_frame = tk .Frame (self .root , bg = '#1f2f3a' )
78- password_frame .pack (pady = 10 )
79- tk .Label (password_frame , text = 'Password' , font = ('Helvetica' , 12 ), bg = '#1f2f3a' , fg = '#bdc3c7' ).pack (anchor = 'w' )
80- self .reg_password = tk .Entry (password_frame , font = ('Helvetica' , 14 ), bd = 0 , highlightthickness = 2 , highlightbackground = '#27ae60' , show = '*' , width = 30 )
81- self .reg_password .pack (pady = 5 , ipady = 6 )
82-
83- # Confirm Password
84- confirm_frame = tk .Frame (self .root , bg = '#1f2f3a' )
85- confirm_frame .pack (pady = 10 )
86- tk .Label (confirm_frame , text = 'Confirm Password' , font = ('Helvetica' , 12 ), bg = '#1f2f3a' , fg = '#bdc3c7' ).pack (anchor = 'w' )
87- self .reg_confirm = tk .Entry (confirm_frame , font = ('Helvetica' , 14 ), bd = 0 , highlightthickness = 2 , highlightbackground = '#27ae60' , show = '*' , width = 30 )
88- self .reg_confirm .pack (pady = 5 , ipady = 6 )
89-
90- # Buttons
91- btn_frame = tk .Frame (self .root , bg = '#1f2f3a' )
92- btn_frame .pack (pady = 20 )
93-
94- register_btn = tk .Button (btn_frame , text = 'Register' , command = self .register , bg = '#27ae60' , fg = 'white' , font = ('Helvetica' , 12 , 'bold' ), width = 25 , height = 2 , bd = 0 , activebackground = '#2ecc71' , cursor = 'hand2' )
95- register_btn .pack (pady = 5 )
96- back_btn = tk .Button (btn_frame , text = 'Back to Login' , command = self .create_login_screen , bg = '#c0392b' , fg = 'white' , font = ('Helvetica' , 12 , 'bold' ), width = 25 , height = 2 , bd = 0 , activebackground = '#e74c3c' , cursor = 'hand2' )
97- back_btn .pack (pady = 5 )
125+
126+ tk .Label (
127+ self .root ,
128+ text = 'Create Account' ,
129+ font = ('Helvetica' , 28 , 'bold' ),
130+ bg = '#1f2f3a' ,
131+ fg = '#ffffff'
132+ ).pack (pady = 30 )
133+
134+ frame = tk .Frame (self .root , bg = '#1f2f3a' )
135+ frame .pack (pady = 10 )
136+
137+ tk .Label (frame , text = 'Username' , fg = '#bdc3c7' , bg = '#1f2f3a' ).pack (anchor = 'w' )
138+ self .reg_username = tk .Entry (frame , font = ('Helvetica' , 14 ), width = 30 )
139+ self .reg_username .pack (ipady = 6 )
140+
141+ frame = tk .Frame (self .root , bg = '#1f2f3a' )
142+ frame .pack (pady = 10 )
143+
144+ tk .Label (frame , text = 'Password' , fg = '#bdc3c7' , bg = '#1f2f3a' ).pack (anchor = 'w' )
145+ self .reg_password = tk .Entry (frame , font = ('Helvetica' , 14 ), show = '*' , width = 30 )
146+ self .reg_password .pack (ipady = 6 )
147+
148+ frame = tk .Frame (self .root , bg = '#1f2f3a' )
149+ frame .pack (pady = 10 )
150+
151+ tk .Label (frame , text = 'Confirm Password' , fg = '#bdc3c7' , bg = '#1f2f3a' ).pack (anchor = 'w' )
152+ self .reg_confirm = tk .Entry (frame , font = ('Helvetica' , 14 ), show = '*' , width = 30 )
153+ self .reg_confirm .pack (ipady = 6 )
154+
155+ frame = tk .Frame (self .root , bg = '#1f2f3a' )
156+ frame .pack (pady = 20 )
157+
158+ tk .Button (frame , text = 'Register' , height = 2 , width = 46 , command = self .register ).pack (pady = 5 )
159+ tk .Button (frame , text = 'Back' , height = 2 , width = 46 , command = self .create_login_screen ).pack (pady = 5 )
160+
161+ # -------------------- Logic --------------------
98162
99163 def login (self ):
100164 username = self .username_entry .get ()
101165 password = self .password_entry .get ()
102- hashed = hash_password ( password )
103- if username in self .users and self .users [username ] == hashed :
166+
167+ if username in self .users and verify_password ( password , self .users [username ]) :
104168 messagebox .showinfo ('Success' , f'Welcome { username } !' )
105169 else :
106170 messagebox .showerror ('Error' , 'Invalid username or password' )
@@ -113,25 +177,39 @@ def register(self):
113177 if not username or not password :
114178 messagebox .showerror ('Error' , 'All fields are required' )
115179 return
180+
181+ if username in self .users :
182+ messagebox .showerror ('Error' , 'Username already exists' )
183+ return
184+
116185 if password != confirm :
117186 messagebox .showerror ('Error' , 'Passwords do not match' )
118187 return
119- if username in self .users :
120- messagebox .showerror ('Error' , 'Username already exists' )
188+
189+ strength_error = validate_password_strength (password )
190+ if strength_error :
191+ messagebox .showerror ('Weak Password' , strength_error )
121192 return
122193
123194 self .users [username ] = hash_password (password )
124195 save_users (self .users )
196+
125197 messagebox .showinfo ('Success' , 'Registration successful!' )
126198 self .create_login_screen ()
127199
128200 def forgot_password (self ):
129201 username = self .username_entry .get ()
130202 if username in self .users :
131- messagebox .showinfo ('Password Reset' , f'Password reset link sent to { username } (mock)' )
203+ messagebox .showinfo (
204+ 'Password Reset' ,
205+ f'Password reset link sent to { username } (mock)'
206+ )
132207 else :
133208 messagebox .showerror ('Error' , 'Username not found' )
134209
210+
211+ # -------------------- Run --------------------
212+
135213if __name__ == '__main__' :
136214 root = tk .Tk ()
137215 app = LoginApp (root )
0 commit comments