Password Strength Analyzer

PythontkinterSecurityAPIhashlib

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'[^... ]' , ^ means not 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!