πŸ› οΈToolsShed

Password Policy Tester

Test passwords against configurable policy rules: length, complexity, forbidden words.

Policy Rules

Frequently Asked Questions

Code Implementation

import re
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class PasswordPolicy:
    min_length: int = 8
    max_length: int = 128
    require_uppercase: bool = True
    require_lowercase: bool = True
    require_digit: bool = True
    require_symbol: bool = True
    min_unique_chars: int = 5
    forbidden_patterns: list[str] = field(default_factory=lambda: [
        r"(..)\1{2,}",       # repeated two-char block 3+ times
        r"(.)\1{3,}",         # same char 4+ times in a row
        r"(?i)password",       # literal word "password"
        r"(?i)qwerty",
    ])
    symbol_chars: str = r"!@#$%^&*()-_=+[]{}|;':",./<>?"

@dataclass
class PolicyResult:
    passed: bool
    violations: list[str] = field(default_factory=list)
    score: int = 0  # 0-100

def check_password(password: str, policy: Optional[PasswordPolicy] = None) -> PolicyResult:
    if policy is None:
        policy = PasswordPolicy()

    violations: list[str] = []
    score = 0

    # Length checks
    if len(password) < policy.min_length:
        violations.append(f"Too short: minimum {policy.min_length} characters")
    elif len(password) >= policy.min_length:
        score += 25

    if len(password) > policy.max_length:
        violations.append(f"Too long: maximum {policy.max_length} characters")

    # Character class checks
    if policy.require_uppercase and not re.search(r"[A-Z]", password):
        violations.append("Must contain at least one uppercase letter")
    else:
        score += 15

    if policy.require_lowercase and not re.search(r"[a-z]", password):
        violations.append("Must contain at least one lowercase letter")
    else:
        score += 15

    if policy.require_digit and not re.search(r"\d", password):
        violations.append("Must contain at least one digit")
    else:
        score += 15

    if policy.require_symbol and not re.search(
        f"[{re.escape(policy.symbol_chars)}]", password
    ):
        violations.append("Must contain at least one symbol")
    else:
        score += 15

    # Unique characters
    if len(set(password)) < policy.min_unique_chars:
        violations.append(f"Must use at least {policy.min_unique_chars} different characters")
    else:
        score += 15

    # Forbidden patterns
    for pattern in policy.forbidden_patterns:
        if re.search(pattern, password):
            violations.append(f"Contains forbidden pattern: {pattern}")

    passed = len(violations) == 0
    return PolicyResult(passed=passed, violations=violations, score=min(score, 100))


# Example usage
if __name__ == "__main__":
    tests = ["abc", "Password1!", "C0rrectH0rseBatteryStaple!"]
    policy = PasswordPolicy(min_length=12)
    for pwd in tests:
        result = check_password(pwd, policy)
        print(f"{pwd!r}: passed={result.passed}, score={result.score}")
        for v in result.violations:
            print(f"  - {v}")

Comments & Feedback

Comments are powered by Giscus. Sign in with GitHub to leave a comment.