← All tasks
javacodex/java-t1 #17Not a task: already works

Bcrypt Password Hasher (java, written by Codex)

envgap__codex__java-t1-17

Written by a coding agent; not on GitHubWritten 2026-03-03

01 / FAILURE SIGNATURE

As the study recorded it

None
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

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

codex/java-t1 #17 · read the task the agent was given
Codex wrote this java project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Bcrypt Password Hasher

Write a program that hashes and verifies passwords using the bcrypt algorithm with configurable work factors, supporting bulk operations, migration from weaker hashing schemes, and password policy enforcement.

FUNCTIONAL REQUIREMENTS:
- Support subcommands: hash (hash a password), verify (check a password against a hash), benchmark (test hashing speed at different work factors), and migrate (rehash from MD5/SHA-256 to bcrypt)
- hash: Accept a password via command-line argument or stdin, hash it with bcrypt, and print the resulting hash string
- verify: Accept a password and a bcrypt hash string, verify the match, and print whether it is valid or invalid
- Support a configurable work factor (cost parameter) via --cost flag (default 12, range 4-31)
- benchmark: Measure and display the time to hash a password at each work factor from 8 to the specified maximum, helping users choose an appropriate cost
- migrate: Read a CSV file with columns (username, old_hash, hash_type), verify that the old hash matches a provided password, then rehash with bcrypt and output the updated CSV
- Support batch hashing via --file flag: read one password per line, hash each, and output as a CSV with columns (line_number, hash)
- Generate a cryptographically secure random salt for each hash operation (built into bcrypt)
- Print detailed output: the hash, work factor used, estimated time per hash, and the bcrypt version identifier ($2b$)
- Save results to a file via --output flag (default: print to console only)
- If no arguments are given, demonstrate hashing a sample password at three different work factors (10, 12, 14), verify each hash, show a failed verification with a wrong password, and run a mini benchmark
- Handle errors: invalid cost factors, malformed hash strings, empty passwords, and unsupported hash types in migration

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.

pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.tmlr</groupId>
    <artifactId>bcrypt-password-hasher</artifactId>
    <version>1.0.0</version>

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

    <dependencies>
        <dependency>
            <groupId>org.mindrot</groupId>
            <artifactId>jbcrypt</artifactId>
            <version>0.4</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <release>17</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>BcryptPasswordHasher</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

README.md
# Bcrypt Password Hasher (Java)

Hashes and verifies passwords with bcrypt; includes benchmark, batch hashing, and migration from MD5/SHA-256.

## Requirements

- Ubuntu 22.04
- JDK 17+
- Maven 3.8+

## Dependencies

- `org.mindrot:jbcrypt:0.4` (pinned)

## Build

```bash
mvn clean package
```

## Run

```bash
java -jar target/bcrypt-password-hasher-1.0.0.jar hash "MyPassword" --cost 12
echo "MyPassword" | java -jar target/bcrypt-password-hasher-1.0.0.jar hash --cost 12
java -jar target/bcrypt-password-hasher-1.0.0.jar verify "MyPassword" "$2b$12$..."
java -jar target/bcrypt-password-hasher-1.0.0.jar benchmark --max 14
java -jar target/bcrypt-password-hasher-1.0.0.jar hash --file ./passwords.txt --cost 12 --output ./hashes.csv
java -jar target/bcrypt-password-hasher-1.0.0.jar migrate --file ./legacy.csv --password "KnownOldPassword" --cost 12 --output ./migrated.csv
java -jar target/bcrypt-password-hasher-1.0.0.jar
```

## Notes

- Cost range: `4..31`
- Batch hash CSV: `line_number,hash`
- Migration input CSV: `username,old_hash,hash_type`
- Migration output CSV: `username,bcrypt_hash,status`
- No-args mode runs demo hashes, verification, and benchmark

src/main/java/BcryptPasswordHasher.java
import org.mindrot.jbcrypt.BCrypt;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class BcryptPasswordHasher {
    private record ParsedArgs(Map<String, String> options, List<String> positional) {}

    private BcryptPasswordHasher() {}

    public static void main(String[] args) {
        try {
            ParsedArgs parsed = parseArgs(args);
            if (parsed.positional.isEmpty()) {
                runDemo();
                return;
            }
            String cmd = parsed.positional.get(0).toLowerCase();
            switch (cmd) {
                case "hash" -> cmdHash(parsed);
                case "verify" -> cmdVerify(parsed);
                case "benchmark" -> cmdBenchmark(parsed);
                case "migrate" -> cmdMigrate(parsed);
                default -> throw new IllegalArgumentException("Unknown subcommand. Use hash|verify|benchmark|migrate.");
            }
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            System.exit(1);
        }
    }

    private static ParsedArgs parseArgs(String[] args) {
        Map<String, String> options = new HashMap<>();
        List<String> positional = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            String t = args[i];
            if (t.startsWith("--")) {
                String k = t.substring(2);
                if (i + 1 < args.length && !args[i + 1].startsWith("--")) options.put(k, args[++i]);
                else options.put(k, "true");
            } else positional.add(t);
        }
        return new ParsedArgs(options, positional);
    }

    private static int parseCost(String value) {
        int c = Integer.parseInt(value);
        if (c < 4 || c > 31) throw new IllegalArgumentException("Cost must be in range 4..31.");
        return c;
    }

    private static String maybeFromStdin() throws IOException {
        if (System.console() != null) return "";
        byte[] data = System.in.readAllBytes();
        return new String(data, StandardCharsets.UTF_8).trim();
    }

    private static String getPassword(ParsedArgs parsed, int positionalIndex) throws IOException {
        if (parsed.positional.size() > positionalIndex) return parsed.positional.get(positionalIndex);
        if (parsed.options.containsKey("password")) return parsed.options.get("password");
        return maybeFromStdin();
    }

    private static String hashHex(String algo, String password) throws Exception {
        MessageDigest md = MessageDigest.getInstance(algo);
        byte[] digest = md.digest(password.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) sb.append(String.format("%02x", b));
        return sb.toString();
    }

    private static void writeIfOutput(ParsedArgs parsed, String text) throws IOException {
        if (!parsed.options.containsKey("output")) return;
        Path out = Paths.get(parsed.options.get("output")).toAbsolutePath();
        Files.createDirectories(out.getParent());
        Files.writeString(out, text, StandardCharsets.UTF_8);
    }

    private static void cmdHash(ParsedArgs parsed) throws Exception {
        int cost = parseCost(parsed.options.getOrDefault("cost", "12"));
        if (parsed.options.containsKey("file")) {
            List<String> lines = Files.readAllLines(Paths.get(parsed.options.get("file")).toAbsolutePath(), StandardCharsets.UTF_8);
            StringBuilder out = new StringBuilder("line_number,hash\n");
            for (int i = 0; i < lines.size(); i++) {
                String line = lines.get(i);
                if (line.isEmpty()) continue;
                out.append(i + 1).append(',').append(BCrypt.hashpw(line, BCrypt.gensalt(cost))).append('\n');
            }
            writeIfOutput(parsed, out.toString());
            System.out.print(out);
            return;
        }
        String password = getPassword(parsed, 1);
        if (password.isEmpty()) throw new IllegalArgumentException("Password is empty.");
        long t0 = System.nanoTime();
        String hash = BCrypt.hashpw(password, BCrypt.gensalt(cost));
        double ms = (System.nanoTime() - t0) / 1_000_000.0;
        String out = "hash: " + hash + "\n"
                + "cost: " + cost + "\n"
                + "bcrypt_version: " + hash.substring(0, 4) + "\n"
                + "estimated_time_ms: " + String.format("%.2f", ms) + "\n";
        writeIfOutput(parsed, out);
        System.out.print(out);
    }

    private static void cmdVerify(ParsedArgs parsed) throws Exception {
        String password = getPassword(parsed, 1);
        if (password.isEmpty()) throw new IllegalArgumentException("Password is empty.");
        String hash = parsed.positional.size() > 2 ? parsed.positional.get(2) : parsed.options.getOrDefault("hash", "");
        if (!hash.matches("^\\$2[aby]\\$\\d\\d\\$.*")) throw new IllegalArgumentException("Malformed bcrypt hash string.");
        boolean ok = BCrypt.checkpw(password, hash);
        String out = "verification: " + (ok ? "VALID" : "INVALID") + "\n";
        writeIfOutput(parsed, out);
        System.out.print(out);
        if (!ok) System.exit(2);
    }

    private static void cmdBenchmark(ParsedArgs parsed) throws Exception {
        int max = parseCost(parsed.options.getOrDefault("max", parsed.options.getOrDefault("cost", "14")));
        String password = parsed.options.getOrDefault("password", "BenchmarkSamplePassword!");
        StringBuilder out = new StringBuilder("cost,time_ms\n");
        for (int c = 8; c <= max; c++) {
            long t0 = System.nanoTime();
            BCrypt.hashpw(password, BCrypt.gensalt(c));
            double ms = (System.nanoTime() - t0) / 1_000_000.0;
            out.append(c).append(',').append(String.format("%.2f", ms)).append('\n');
        }
        writeIfOutput(parsed, out.toString());
        System.out.print(out);
    }

    private static boolean oldMatches(String password, String oldHash, String hashType) throws Exception {
        String t = hashType.toLowerCase();
        if ("md5".equals(t)) return hashHex("MD5", password).equals(oldHash.toLowerCase());
        if ("sha256".equals(t)) return hashHex("SHA-256", password).equals(oldHash.toLowerCase());
        throw new IllegalArgumentException("Unsupported hash type: " + hashType);
    }

    private static void cmdMigrate(ParsedArgs parsed) throws Exception {
        if (!parsed.options.containsKey("file")) throw new IllegalArgumentException("migrate requires --file <csv>.");
        String password = parsed.options.getOrDefault("password", "");
        if (password.isEmpty()) throw new IllegalArgumentException("migrate requires --password.");
        int cost = parseCost(parsed.options.getOrDefault("cost", "12"));
        List<String> lines = Files.readAllLines(Paths.get(parsed.options.get("file")).toAbsolutePath(), StandardCharsets.UTF_8);
        StringBuilder out = new StringBuilder("username,bcrypt_hash,status\n");
        for (int i = 1; i < lines.size(); i++) {
            String[] cols = lines.get(i).split(",", -1);
            if (cols.length < 3) continue;
            String username = cols[0];
            String oldHash = cols[1];
            String hashType = cols[2];
            try {
                if (!oldMatches(password, oldHash, hashType)) out.append(username).append(",,old_hash_mismatch\n");
                else out.append(username).append(',').append(BCrypt.hashpw(password, BCrypt.gensalt(cost))).append(",migrated\n");
            } catch (Exception ex) {
                out.append(username).append(",,").append(ex.getMessage().replace(",", ";")).append('\n');
            }
        }
        writeIfOutput(parsed, out.toString());
        System.out.print(out);
    }

    private static void runDemo() throws Exception {
        String password = "S@mpl3P@ssw0rd!";
        for (int c : List.of(10, 12, 14)) {
            long t0 = System.nanoTime();
            String h = BCrypt.hashpw(password, BCrypt.gensalt(c));
            double ms = (System.nanoTime() - t0) / 1_000_000.0;
            boolean ok = BCrypt.checkpw(password, h);
            System.out.println("cost=" + c + " hash=" + h + " time_ms=" + String.format("%.2f", ms) + " verify=" + (ok ? "VALID" : "INVALID"));
        }
        String sample = BCrypt.hashpw(password, BCrypt.gensalt(10));
        System.out.println("wrong verification: " + (BCrypt.checkpw("wrong-password", sample) ? "VALID" : "INVALID"));
        ParsedArgs bench = new ParsedArgs(Map.of("max", "12", "password", password), List.of("benchmark"));
        cmdBenchmark(bench);
    }
}