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);
}
}