← All tasks
javaclaude-code/java-t1 #15Not a task: repair not recorded

Password Strength Analyzer (java, written by Claude Code)

envgap__claude-code__java-t1-15

Written by a coding agent; not on GitHubWritten 2026-02-27

01 / FAILURE SIGNATURE

As the study recorded it

source file in project root instead of src/main/java + no shade plugin
Not a benchmark task.
  • It was made to work, but its repair cannot be rebuilt from the saved files (the saved copy shows no change, or not all of the changes the study's notes describe), so there is no fix to score against.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
pom.xml
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/java-t1 #15 · read the task the agent was given
Claude Code wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Password Strength Analyzer

Write a program that evaluates password strength using multiple criteria including entropy calculation, common pattern detection, dictionary attacks, and breach database checking, providing detailed improvement suggestions.

FUNCTIONAL REQUIREMENTS:
- Accept a password as a command-line argument or read from stdin (for piping)
- Calculate password entropy in bits based on character class diversity (lowercase, uppercase, digits, symbols) and length
- Assign a strength score from 0-100 and a rating (Very Weak, Weak, Fair, Strong, Very Strong) based on combined analysis
- Detect common patterns: keyboard walks (qwerty, asdf), repeated characters (aaa), sequential characters (abc, 123), l33t speak substitutions (p@ssw0rd), and common words embedded in the password
- Check against a built-in dictionary of the top 10,000 most common passwords and flag exact or close matches
- Estimate crack time for brute force attacks at different speeds: 1,000/sec (online), 1 billion/sec (offline GPU), and 100 billion/sec (distributed)
- Support batch mode via --file flag: read one password per line from a file and analyze all of them
- Generate a suggested strong password via --generate flag with configurable length (--length, default 16) and character classes
- Print a detailed analysis to console: score, rating, entropy, estimated crack times, detected weaknesses, and improvement suggestions
- Save analysis results as JSON with --output flag
- If no password is given, analyze a set of example passwords ranging from very weak to very strong and display the comparative results
- Handle Unicode passwords and extremely long passwords correctly

Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

PasswordAnalyzer.java
import org.passay.*;
import org.passay.dictionary.ArrayWordList;
import org.passay.dictionary.WordListDictionary;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Password Strength Analyzer
 *
 * Evaluates password strength via entropy calculation, pattern detection,
 * dictionary checks, and crack time estimation using Passay and Gson.
 */
public class PasswordAnalyzer {

    private static final String[] COMMON_PASSWORDS = {
        "password", "123456", "12345678", "qwerty", "abc123", "monkey",
        "master", "dragon", "111111", "baseball", "iloveyou", "trustno1",
        "sunshine", "letmein", "welcome", "shadow", "superman", "michael",
        "football", "password1", "password123", "admin", "login", "hello"
    };

    private final PasswordValidator validator;
    private final Set<String> dictionaryWords;
    private final Gson gson;

    public PasswordAnalyzer() {
        // Build Passay rules for password validation
        List<Rule> rules = new ArrayList<>();
        rules.add(new LengthRule(8, 128));
        rules.add(new CharacterRule(EnglishCharacterData.UpperCase, 1));
        rules.add(new CharacterRule(EnglishCharacterData.LowerCase, 1));
        rules.add(new CharacterRule(EnglishCharacterData.Digit, 1));
        rules.add(new CharacterRule(EnglishCharacterData.Special, 1));
        rules.add(new WhitespaceRule());
        rules.add(new RepeatCharacterRegexRule(3));

        // Dictionary rule using common passwords
        ArrayWordList wordList = new ArrayWordList(COMMON_PASSWORDS, false, new ArrayWordList.DefaultComparator());
        WordListDictionary dictionary = new WordListDictionary(wordList);
        rules.add(new DictionaryRule(dictionary));

        this.validator = new PasswordValidator(rules);
        this.dictionaryWords = new HashSet<>(Arrays.asList(COMMON_PASSWORDS));
        this.gson = new GsonBuilder().setPrettyPrinting().create();
    }

    /**
     * Analyze the strength of a password.
     *
     * @param password the password to analyze
     * @return a map containing the full analysis results
     */
    public Map<String, Object> analyze(String password) {
        Map<String, Object> result = new LinkedHashMap<>();

        if (password == null || password.isEmpty()) {
            result.put("password", "");
            result.put("length", 0);
            result.put("score", 0);
            result.put("scoreLabel", "Empty");
            result.put("entropy", 0.0);
            result.put("feedback", Collections.singletonList("Password is empty."));
            return result;
        }

        int length = password.length();
        String masked = maskPassword(password);

        result.put("password", masked);
        result.put("length", length);

        // Character composition analysis
        Map<String, Object> composition = analyzeComposition(password);
        result.put("composition", composition);

        // Entropy calculation
        double entropy = calculateEntropy(password);
        result.put("entropy", Math.round(entropy * 100.0) / 100.0);

        // Passay validation
        RuleResult ruleResult = validator.validate(new PasswordData(password));
        List<String> violations = new ArrayList<>();
        if (!ruleResult.isValid()) {
            for (RuleResultDetail detail : ruleResult.getDetails()) {
                violations.add(detail.getErrorCode());
            }
        }
        result.put("ruleViolations", violations);

        // Pattern detection
        List<Map<String, Object>> patterns = detectPatterns(password);
        result.put("patternsDetected", patterns);

        // Dictionary check
        boolean inDictionary = isDictionaryWord(password);
        result.put("dictionaryMatch", inDictionary);

        // Score calculation
        int score = calculateScore(password, entropy, violations.size(), patterns.size(), inDictionary);
        result.put("score", score);
        result.put("scoreLabel", getScoreLabel(score));

        // Crack time estimation
        Map<String, String> crackTimes = estimateCrackTimes(entropy);
        result.put("crackTimes", crackTimes);

        // Feedback
        List<String> feedback = generateFeedback(password, violations, patterns, inDictionary, score);
        result.put("feedback", feedback);

        return result;
    }

    private String maskPassword(String password) {
        if (password.length() <= 2) {
            return "*".repeat(password.length());
        }
        return password.charAt(0) + "*".repeat(password.length() - 2) + password.charAt(password.length() - 1);
    }

    private Map<String, Object> analyzeComposition(String password) {
        Map<String, Object> comp = new LinkedHashMap<>();
        int upper = 0, lower = 0, digits = 0, special = 0;
        Set<Character> uniqueChars = new HashSet<>();

        for (char c : password.toCharArray()) {
            uniqueChars.add(c);
            if (Character.isUpperCase(c)) upper++;
            else if (Character.isLowerCase(c)) lower++;
            else if (Character.isDigit(c)) digits++;
            else special++;
        }

        comp.put("uppercase", upper);
        comp.put("lowercase", lower);
        comp.put("digits", digits);
        comp.put("special", special);
        comp.put("uniqueCharacters", uniqueChars.size());

        return comp;
    }

    /**
     * Calculate Shannon entropy of the password.
     */
    private double calculateEntropy(String password) {
        // Determine charset size
        int charsetSize = 0;
        boolean hasLower = false, hasUpper = false, hasDigit = false, hasSpecial = false;

        for (char c : password.toCharArray()) {
            if (Character.isLowerCase(c)) hasLower = true;
            else if (Character.isUpperCase(c)) hasUpper = true;
            else if (Character.isDigit(c)) hasDigit = true;
            else hasSpecial = true;
        }

        if (hasLower) charsetSize += 26;
        if (hasUpper) charsetSize += 26;
        if (hasDigit) charsetSize += 10;
        if (hasSpecial) charsetSize += 33;

        if (charsetSize == 0) return 0.0;

        return password.length() * (Math.log(charsetSize) / Math.log(2));
    }

    private List<Map<String, Object>> detectPatterns(String password) {
        List<Map<String, Object>> patterns = new ArrayList<>();

        // Check for sequential characters
        detectSequentialPattern(password, patterns);

        // Check for repeated characters
        detectRepeatPattern(password, patterns);

        // Check for keyboard patterns
        detectKeyboardPattern(password, patterns);

        // Check for date patterns
        detectDatePattern(password, patterns);

        // Check for common substitutions (l33t speak)
        detectL33tPattern(password, patterns);

        return patterns;
    }

    private void detectSequentialPattern(String password, List<Map<String, Object>> patterns) {
        String lower = password.toLowerCase();
        for (int i = 0; i < lower.length() - 2; i++) {
            if (lower.charAt(i + 1) == lower.charAt(i) + 1 && lower.charAt(i + 2) == lower.charAt(i) + 2) {
                int end = i + 2;
                while (end + 1 < lower.length() && lower.charAt(end + 1) == lower.charAt(end) + 1) {
                    end++;
                }
                Map<String, Object> p = new LinkedHashMap<>();
                p.put("type", "sequential");
                p.put("token", password.substring(i, end + 1));
                p.put("start", i);
                p.put("end", end);
                patterns.add(p);
                break;
            }
        }
    }

    private void detectRepeatPattern(String password, List<Map<String, Object>> patterns) {
        Pattern repeat = Pattern.compile("(.)\\1{2,}");
        Matcher m = repeat.matcher(password);
        while (m.find()) {
            Map<String, Object> p = new LinkedHashMap<>();
            p.put("type", "repeat");
            p.put("token", m.group());
            p.put("start", m.start());
            p.put("end", m.end() - 1);
            p.put("repeatedChar", String.valueOf(m.group(1)));
            patterns.add(p);
        }
    }

    private void detectKeyboardPattern(String password, List<Map<String, Object>> patterns) {
        String[] rows = {"qwertyuiop", "asdfghjkl", "zxcvbnm", "1234567890"};
        String lower = password.toLowerCase();
        for (String row : rows) {
            for (int len = 4; len <= lower.length(); len++) {
                for (int i = 0; i <= lower.length() - len; i++) {
                    String sub = lower.substring(i, i + len);
                    if (row.contains(sub)) {
                        Map<String, Object> p = new LinkedHashMap<>();
                        p.put("type", "keyboard");
                        p.put("token", password.substring(i, i + len));
                        p.put("start", i);
                        p.put("end", i + len - 1);
                        patterns.add(p);
                    }
                }
            }
        }
    }

    private void detectDatePattern(String password, List<Map<String, Object>> patterns) {
        Pattern datePattern = Pattern.compile("(19|20)\\d{2}[\\-/]?(0[1-9]|1[0-2])[\\-/]?(0[1-9]|[12]\\d|3[01])");
        Matcher m = datePattern.matcher(password);
        while (m.find()) {
            Map<String, Object> p = new LinkedHashMap<>();
            p.put("type", "date");
            p.put("token", m.group());
            p.put("start", m.start());
            p.put("end", m.end() - 1);
            patterns.add(p);
        }
    }

    private void detectL33tPattern(String password, List<Map<String, Object>> patterns) {
        Map<Character, Character> l33tMap = new HashMap<>();
        l33tMap.put('0', 'o');
        l33tMap.put('1', 'l');
        l33tMap.put('3', 'e');
        l33tMap.put('4', 'a');
        l33tMap.put('5', 's');
        l33tMap.put('7', 't');
        l33tMap.put('@', 'a');
        l33tMap.put('$', 's');
        l33tMap.put('!', 'i');

        StringBuilder decoded = new StringBuilder();
        boolean hasL33t = false;
        for (char c : password.toLowerCase().toCharArray()) {
            if (l33tMap.containsKey(c)) {
                decoded.append(l33tMap.get(c));
                hasL33t = true;
            } else {
                decoded.append(c);
            }
        }

        if (hasL33t) {
            String decodedStr = decoded.toString();
            for (String word : COMMON_PASSWORDS) {
                if (decodedStr.contains(word)) {
                    Map<String, Object> p = new LinkedHashMap<>();
                    p.put("type", "l33t");
                    p.put("decodedToken", word);
                    p.put("originalToken", password);
                    patterns.add(p);
                    break;
                }
            }
        }
    }

    private boolean isDictionaryWord(String password) {
        String lower = password.toLowerCase();
        return dictionaryWords.contains(lower);
    }

    private int calculateScore(String password, double entropy, int violations, int patternCount, boolean inDictionary) {
        int score = 0;

        if (entropy >= 25) score++;
        if (entropy >= 40) score++;
        if (entropy >= 60) score++;
        if (entropy >= 80) score++;

        // Penalties
        if (violations > 3) score = Math.max(0, score - 2);
        else if (violations > 0) score = Math.max(0, score - 1);

        if (patternCount > 2) score = Math.max(0, score - 1);
        if (inDictionary) score = Math.max(0, score - 2);

        if (password.length() < 6) score = 0;

        return Math.min(score, 4);
    }

    private String getScoreLabel(int score) {
        switch (score) {
            case 0: return "Very Weak";
            case 1: return "Weak";
            case 2: return "Fair";
            case 3: return "Strong";
            case 4: return "Very Strong";
            default: return "Unknown";
        }
    }

    private Map<String, String> estimateCrackTimes(double entropy) {
        double guesses = Math.pow(2, entropy);
        Map<String, String> times = new LinkedHashMap<>();

        // Online throttled (100 guesses/hour)
        times.put("onlineThrottled", formatTime(guesses / 100.0 * 3600));
        // Online unthrottled (10 guesses/sec)
        times.put("onlineUnthrottled", formatTime(guesses / 10.0));
        // Offline slow hash (10k guesses/sec)
        times.put("offlineSlowHash", formatTime(guesses / 1e4));
        // Offline fast hash (10B guesses/sec)
        times.put("offlineFastHash", formatTime(guesses / 1e10));

        return times;
    }

    private String formatTime(double seconds) {
        if (seconds < 1) return "instant";
        if (seconds < 60) return String.format("%.0f seconds", seconds);
        if (seconds < 3600) return String.format("%.0f minutes", seconds / 60);
        if (seconds < 86400) return String.format("%.0f hours", seconds / 3600);
        if (seconds < 2592000) return String.format("%.0f days", seconds / 86400);
        if (seconds < 31536000) return String.format("%.0f months", seconds / 2592000);
        if (seconds < 3153600000.0) return String.format("%.0f years", seconds / 31536000);
        return "centuries";
    }

    private List<String> generateFeedback(String password, List<String> violations,
                                           List<Map<String, Object>> patterns,
                                           boolean inDictionary, int score) {
        List<String> feedback = new ArrayList<>();

        if (password.length() < 8) {
            feedback.add("Use at least 8 characters.");
        }
        if (inDictionary) {
            feedback.add("This is a commonly used password. Choose something more unique.");
        }
        for (String v : violations) {
            switch (v) {
                case "INSUFFICIENT_UPPERCASE":
                    feedback.add("Add uppercase letters for better strength.");
                    break;
                case "INSUFFICIENT_LOWERCASE":
                    feedback.add("Add lowercase letters for better strength.");
                    break;
                case "INSUFFICIENT_DIGIT":
                    feedback.add("Add numbers for better strength.");
                    break;
                case "INSUFFICIENT_SPECIAL":
                    feedback.add("Add special characters (e.g., !@#$%) for better strength.");
                    break;
                case "ILLEGAL_WHITESPACE":
                    feedback.add("Avoid using spaces in your password.");
                    break;
                case "ILLEGAL_MATCH":
                    feedback.add("Avoid repeated character sequences.");
                    break;
            }
        }
        for (Map<String, Object> p : patterns) {
            String type = (String) p.get("type");
            if ("sequential".equals(type)) {
                feedback.add("Avoid sequential characters like 'abc' or '123'.");
            } else if ("keyboard".equals(type)) {
                feedback.add("Avoid keyboard patterns like 'qwerty'.");
            } else if ("l33t".equals(type)) {
                feedback.add("Simple letter substitutions (e.g., '@' for 'a') are easily guessed.");
            }
        }
        if (score >= 3 && feedback.isEmpty()) {
            feedback.add("Good password! No major issues detected.");
        }

        return feedback;
    }

    /**
     * Generate a formatted text report for the analysis.
     */
    public String formatReport(Map<String, Object> analysis) {
        StringBuilder sb = new StringBuilder();
        sb.append("=".repeat(60)).append("\n");
        sb.append("       PASSWORD STRENGTH ANALYSIS REPORT\n");
        sb.append("=".repeat(60)).append("\n");
        sb.append("  Password (masked): ").append(analysis.get("password")).append("\n");
        sb.append("  Length:            ").append(analysis.get("length")).append(" characters\n");
        sb.append("  Score:             ").append(analysis.get("score")).append("/4 - ")
          .append(analysis.get("scoreLabel")).append("\n");
        sb.append("  Entropy:           ").append(analysis.get("entropy")).append(" bits\n\n");

        @SuppressWarnings("unchecked")
        Map<String, String> crackTimes = (Map<String, String>) analysis.get("crackTimes");
        if (crackTimes != null) {
            sb.append("  Crack Time Estimates:\n");
            sb.append("  ").append("-".repeat(50)).append("\n");
            for (Map.Entry<String, String> entry : crackTimes.entrySet()) {
                sb.append("    ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
            }
            sb.append("\n");
        }

        @SuppressWarnings("unchecked")
        List<String> feedback = (List<String>) analysis.get("feedback");
        if (feedback != null && !feedback.isEmpty()) {
            sb.append("  Feedback:\n");
            for (String f : feedback) {
                sb.append("    - ").append(f).append("\n");
            }
        }

        sb.append("=".repeat(60));
        return sb.toString();
    }

    /**
     * Get analysis results as a JSON string.
     */
    public String toJson(Map<String, Object> analysis) {
        return gson.toJson(analysis);
    }

    public static void main(String[] args) {
        PasswordAnalyzer analyzer = new PasswordAnalyzer();

        String[] passwords;
        if (args.length > 0) {
            passwords = args;
        } else {
            passwords = new String[]{"password123", "Tr0ub4dor&3", "correcthorsebatterystaple", "9f$K#mP!xQ2v"};
            System.out.println("No passwords provided. Analyzing examples...\n");
        }

        for (String password : passwords) {
            Map<String, Object> analysis = analyzer.analyze(password);
            System.out.println(analyzer.formatReport(analysis));
            System.out.println("\nJSON Output:");
            System.out.println(analyzer.toJson(analysis));
            System.out.println();
        }
    }
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>password-analyzer</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>Password Strength Analyzer</name>
    <description>Evaluates password strength via entropy, pattern detection, dictionary checks, and crack time estimation.</description>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.passay</groupId>
            <artifactId>passay</artifactId>
            <version>1.6.4</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>PasswordAnalyzer</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
README.md
# Password Strength Analyzer - Java (Trial 1)

A command-line tool that evaluates password strength using entropy calculation, pattern detection, dictionary checks, and crack time estimation.

## Dependencies

- **Passay** (1.6.4): A Java password policy enforcement library providing rule-based validation, character requirements, and dictionary checking.
- **Gson** (2.10.1): A Google library for serializing/deserializing Java objects to/from JSON format.

## Setup

```bash
mvn clean compile
```

## Usage

Run with Maven:

```bash
mvn exec:java -Dexec.mainClass="PasswordAnalyzer" -Dexec.args="'mypassword' 'Str0ng!P@ss'"
```

Or build and run the JAR:

```bash
mvn clean package
java -jar target/password-analyzer-1.0.0.jar "mypassword" "Str0ng!P@ss"
```

## Features

- Rule-based password validation with Passay
- Shannon entropy calculation
- Pattern detection (sequential, keyboard, repeat, date, l33t)
- Common password dictionary checking
- Crack time estimation for multiple attack scenarios
- JSON and formatted text output