Local Password Strength Analyzer

import tkinter as tk

from tkinter import ttk

import math

import re


# ---------------------------

# Heuristics & helpers

# ---------------------------


COMMON_PASSWORDS = {

    "password", "123456", "123456789", "qwerty", "abc123",

    "letmein", "welcome", "admin", "iloveyou", "monkey"

}


SEQUENCES = [

    "abcdefghijklmnopqrstuvwxyz",

    "ABCDEFGHIJKLMNOPQRSTUVWXYZ",

    "0123456789",

    "qwertyuiop", "asdfghjkl", "zxcvbnm"

]


def shannon_entropy(password: str) -> float:

    """Approximate entropy based on character sets used."""

    pool = 0

    if re.search(r"[a-z]", password): pool += 26

    if re.search(r"[A-Z]", password): pool += 26

    if re.search(r"\d", password):    pool += 10

    if re.search(r"[^\w\s]", password): pool += 32  # symbols

    if pool == 0:

        return 0.0

    return len(password) * math.log2(pool)


def has_repeats(password: str) -> bool:

    return bool(re.search(r"(.)\1{2,}", password))  # aaa, 111


def has_sequence(password: str) -> bool:

    p = password

    for seq in SEQUENCES:

        for i in range(len(seq) - 3):

            if seq[i:i+4] in p:

                return True

    return False


def keyboard_walk(password: str) -> bool:

    walks = ["qwerty", "asdf", "zxcv", "poiuy", "lkjhg", "mnbv"]

    p = password.lower()

    return any(w in p for w in walks)


def analyze_password(password: str):

    issues = []

    score = 0


    # Length

    if len(password) < 8:

        issues.append("Too short (minimum 8 characters).")

    else:

        score += min(20, (len(password) - 7) * 3)


    # Character classes

    classes = 0

    classes += bool(re.search(r"[a-z]", password))

    classes += bool(re.search(r"[A-Z]", password))

    classes += bool(re.search(r"\d", password))

    classes += bool(re.search(r"[^\w\s]", password))

    score += classes * 10


    if classes < 3:

        issues.append("Use at least 3 character types (upper/lower/digits/symbols).")


    # Entropy

    ent = shannon_entropy(password)

    score += min(30, int(ent / 3))  # cap contribution


    if ent < 40:

        issues.append("Low entropy. Add length and variety.")


    # Patterns

    if password.lower() in COMMON_PASSWORDS:

        issues.append("Common password detected.")

        score -= 30


    if has_repeats(password):

        issues.append("Repeated characters detected (e.g., 'aaa').")

        score -= 10


    if has_sequence(password):

        issues.append("Sequential pattern detected (e.g., 'abcd', '1234').")

        score -= 10


    if keyboard_walk(password):

        issues.append("Keyboard pattern detected (e.g., 'qwerty').")

        score -= 10


    score = max(0, min(100, score))


    if score >= 80:

        verdict = "Strong"

    elif score >= 60:

        verdict = "Good"

    elif score >= 40:

        verdict = "Weak"

    else:

        verdict = "Very Weak"


    return {

        "score": score,

        "verdict": verdict,

        "entropy": round(ent, 1),

        "issues": issues

    }


# ---------------------------

# GUI

# ---------------------------


class App(tk.Tk):

    def __init__(self):

        super().__init__()

        self.title("Local Password Strength Analyzer")

        self.geometry("520x420")

        self.resizable(False, False)


        ttk.Label(self, text="Enter Password:", font=("Arial", 12)).pack(pady=10)

        self.pw = ttk.Entry(self, show="*", width=40)

        self.pw.pack()


        self.show_var = tk.BooleanVar(value=False)

        ttk.Checkbutton(self, text="Show password", variable=self.show_var,

                        command=self.toggle_show).pack(pady=5)


        ttk.Button(self, text="Analyze", command=self.run).pack(pady=10)


        self.score_lbl = ttk.Label(self, text="Score: —", font=("Arial", 12, "bold"))

        self.score_lbl.pack()


        self.ent_lbl = ttk.Label(self, text="Entropy: — bits")

        self.ent_lbl.pack(pady=5)


        self.verdict_lbl = ttk.Label(self, text="Verdict: —", font=("Arial", 12))

        self.verdict_lbl.pack(pady=5)


        ttk.Label(self, text="Feedback:", font=("Arial", 11, "bold")).pack(pady=8)

        self.feedback = tk.Text(self, height=8, width=60, wrap="word")

        self.feedback.pack(padx=10)


    def toggle_show(self):

        self.pw.config(show="" if self.show_var.get() else "*")


    def run(self):

        pwd = self.pw.get()

        res = analyze_password(pwd)


        self.score_lbl.config(text=f"Score: {res['score']}/100")

        self.ent_lbl.config(text=f"Entropy: {res['entropy']} bits")

        self.verdict_lbl.config(text=f"Verdict: {res['verdict']}")


        self.feedback.delete("1.0", "end")

        if res["issues"]:

            for i in res["issues"]:

                self.feedback.insert("end", f"• {i}\n")

        else:

            self.feedback.insert("end", "No issues found. Great password!")


if __name__ == "__main__":

    App().mainloop()