Password Strength Analyzer
Password Strength Analyzer
Phase 01
First I implement methods to get character counts of upper case, lower case, digits, and symbols separately. to get that I used python regex library
import re
def upper_case_count(word):
return len(re.findall(r'[A-Z]', word))
def lower_case_count(word):
return len(re.findall(r'[a-z]', word))
def digit_count(word):
return len(re.findall(r'\d', word))
def symbol_count(word):
return len(re.findall(r'[^a-zA-Z0-9\s]', word))
In
r'[^... ]', ^ meansnot these...
Then define pool size for calculate entropy of the password
import math
def pool_size(word):
pool = 0
if upper_case_count(word) > 0:
pool += 26
if lower_case_count(word) > 0:
pool += 26
if digit_count(word) > 0:
pool += 10
if symbol_count(word) > 0:
pool += 32
return pool
def entropy(word):
if len(word) == 0:
return 0
return len(word)*math.log2(pool_size(word))
Then got a leaked password list of 100k passwords and create a method to check weather the current password is in there or not. And give a score for a password from out of 7.
def password_score(word, common_pwds=None):
if common_pwds is not None and word in common_pwds:
return 0
score = len(word)//4
if score > 3:
score = 3
if upper_case_count(word) > 0:
score += 1
if lower_case_count(word) > 0:
score += 1
if digit_count(word) > 0:
score += 1
if symbol_count(word) > 0:
score += 1
return score
def load_common_passwords():
with open('commonpwd.txt', 'r', encoding='utf-8') as fi:
return {line.strip() for line in fi}
To get passwords from the list.
Phase 02
Create a method to hash a password using SHA-1 algorithm (Secure Hash Algorithm)
![[Pasted image 20260218055357.png]]
This hash function which takes an input and produces a 160-bit (20-byte) hash value known as a message digest – typically rendered as 40 hexadecimal digits. hashlib.sha1(...)
Oh and forgot... We have to first import hash library for python hashlib.
Since we are getting passwords there may be special symbols we have to deal with so we will use encoding to ensure standard characters are gone to the hash function.
Then we convert raw binary hash into hexadecimal using .hexdigest(). Since it return lower case and API contain upper case chars, we use .upper() to convert hexadecimal value. Or simply standardization.
import hashlib
def hash_password(password):
password_bytes = password.encode('utf-8')
sha1_hash = hashlib.sha1(password_bytes).hexdigest()
return sha1_hash.upper()
Then we will use Have I Been Pwned (HIBP) API to cheack for data breaches. But we dont send your password. Its too much risky and meaningless if so.
Instead we send 5 characters and request all breached passwords similar to that. In that way they will not know the password you are trying to cheack.
we will have to use import requests library to send and get the response from API
def check_password_breach(password):
sha1_hash = hash_password(password)
prefix = sha1_hash[:5]
suffix = sha1_hash[5:]
# Have I Been Pwned (HIBP) API
url = f"https://api.pwnedpasswords.com/range/{prefix}"
try:
headers = {'User-Agent': 'Password-Strength-Analyzer-Project'}
response = requests.get(url, headers=headers)
if response.status_code != 200:
return None
for lines in response.text.splitlines():
line_suffix, count = lines.split(":")
if line_suffix == suffix:
return int(count)
return 0
except requests.exceptions.RequestException:
return None
We use prefix (or first 5 chars) to get the similar passwords and check them locally using suffix (or rest of the password) to verify are there any exact ones.
This method call, Privacy (k-Anonymity).
The HIBP API strictly requires a User-Agent. If you don't send one, the server will return a 403 Forbidden error. That's why we send headers.
response.status_code == 200 check weather site successfully return the data.
for lines in response.text.splitlines():
line_suffix, count = lines.split(":")
if line_suffix == suffix:
return int(count)
The API returns a long list of strings where each line looks like this, 0018A05B362956DC4AC992199131B105F70:532. Suffix : # of times it breach.
Phase 03
Create a graphical user interface (GUI) so users don't have to test passwords inside a boring terminal. We will use the built-in tkinter library.
Oh and forgot... We have to import tkinter, plus tkinter.ttk (for modern looking widgets like the progress bar), and threading to keep our internet checks from freezing the app.
Since we are testing passwords in real-time as the user types, we need a function that triggers every time a key is released (<KeyRelease> event).
import tkinter as tk
import tkinter.ttk as ttk
import threading
import product1 as pro1
import product2 as pro2
def update_analysis(event=None):
password = password_typed.get()
# Calculate score & entropy locally
score = pro1.password_score(password, common_pwds=blacklist)
bits = pro1.entropy(password)
score_label.config(text=f"Score: {score} / 7")
entropy_label.config(text=f"Entropy: {bits:.2f} bits")
Then we use a visual strength meter. Instead of just printing "Weak", we map our score/7 to a 0-100 progress bar value.
On Windows, colors on the progress bar can be tricky, so we use ttk.Style() and force it to use the 'clam' theme so our custom colors actually apply.
Based on the score returned by the analyzer, we change the color: Red for weak, Orange for medium, Green for strong, and Purple for very strong.
progress_val = (score / 7) * 100
strength_bar['value'] = progress_val
style = ttk.Style()
style.theme_use('clam')
if 0 <= score < 3:
score_label.config(fg="red")
style.configure("red.Horizontal.TProgressbar", foreground='red', background='red')
strength_bar.config(style="red.Horizontal.TProgressbar")
# ... logic continues for orange, green, purple
Now here's the real problem. HIBP API calls in product2.py take actual time to get a response over the internet.
If we run them normally, the entire GUI locks up and freezes while waiting. Meaningless if the user can't even type!
Instead, we use a background thread to call the HIBP API silently while the user keeps typing. daemon=True ensures the thread dies quietly if the user closes the app.
if password == "":
breach_label.config(text="", fg="black")
else:
breach_label.config(text="Breach Check: Checking...", fg="blue")
# Start the background worker
threading.Thread(target=check_breach_thread, args=(password,),daemon=True).start()
When this thread eventually gets an answer from the API, we have a new problem. Background threads are not allowed to touch the actual GUI widgets directly!
To fix this, we create an update_ui function inside the thread, and hand it to root.after(0, ...) to safely tell the main window to update our breach label.
def check_breach_thread(password_to_check):
try:
# Call the API you made in Phase 02
count = pro2.check_password_breach(password_to_check)
# Define a safe UI update function
def update_ui():
if count is None:
breach_label.config(text="Breach Check: API Error / No Internet", fg="orange")
elif count > 0:
breach_label.config(text=f"Breach Check: Pwned {count:,} times!", fg="red")
else:
breach_label.config(text="Breach Check: Clean", fg="green")
# Send it back to the safe main GUI thread
root.after(0, update_ui)
except Exception as e:
print(f"Thread error: {e}")
Finally, we also added a "Show Password" checkbox. By default, tk.Entry hides the characters using show="*".
When the user clicks the checkbox, we run a toggle function that simply removes the asterisk to reveal the text, or puts it back to hide it.
def toggle_password():
if show_password_var.get():
password_typed.config(show="") # Show text
else:
password_typed.config(show="*") # Hide text
This makes the whole thing feel like a modern, responsive application!