Bcrypt Password Hasher (python, written by Codex)
envgap__codex__python-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
requirements.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/python-t1 #17 · read the task the agent was given
Codex wrote this python 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 Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include: - Source code - requirements.txt 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.
README.md
# Bcrypt Password Hasher (Python) Hashes and verifies passwords with bcrypt, includes benchmark, bulk hashing, and migration from MD5/SHA-256. ## Requirements - Ubuntu 22.04 - Python 3.10+ ## Dependencies (Pinned) - `bcrypt==4.2.0` - `cffi==1.17.1` - `pycparser==2.22` ## Setup ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` ## Run ```bash python src/main.py hash "MyPassword" --cost 12 echo "MyPassword" | python src/main.py hash --cost 12 python src/main.py verify "MyPassword" "$2b$12$..." python src/main.py benchmark --max-cost 14 python src/main.py hash --file ./passwords.txt --cost 12 --output ./hashes.csv python src/main.py migrate --file ./legacy.csv --password "KnownOldPassword" --cost 12 --output ./migrated.csv python src/main.py ``` ## Notes - Cost range: `4..31` - Batch hash output 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
requirements.txt
bcrypt==4.2.0 cffi==1.17.1 pycparser==2.22
src/main.py
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import csv
import hashlib
import sys
import time
from pathlib import Path
import bcrypt
def parse_cost(value: int) -> int:
if value < 4 or value > 31:
raise ValueError("Cost must be in range 4..31.")
return value
def read_password(cli_password: str | None) -> str:
if cli_password is not None:
return cli_password
if not sys.stdin.isatty():
return sys.stdin.read().rstrip("\n")
return ""
def ensure_password(password: str) -> None:
if not password:
raise ValueError("Password is empty.")
def hash_one(password: str, cost: int) -> tuple[str, float]:
t0 = time.perf_counter()
hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt(rounds=cost))
ms = (time.perf_counter() - t0) * 1000.0
return hashed.decode("utf-8"), ms
def cmd_hash(args: argparse.Namespace) -> int:
cost = parse_cost(args.cost)
if args.file:
rows = [("line_number", "hash")]
for i, line in enumerate(Path(args.file).read_text(encoding="utf-8").splitlines(), start=1):
if not line:
continue
h, _ = hash_one(line, cost)
rows.append((str(i), h))
out = "\n".join([",".join(r) for r in rows]) + "\n"
if args.output:
Path(args.output).write_text(out, encoding="utf-8")
print(out, end="")
return 0
password = read_password(args.password)
ensure_password(password)
h, ms = hash_one(password, cost)
out = f"hash: {h}\ncost: {cost}\nbcrypt_version: {h[:4]}\nestimated_time_ms: {ms:.2f}\n"
if args.output:
Path(args.output).write_text(out, encoding="utf-8")
print(out, end="")
return 0
def cmd_verify(args: argparse.Namespace) -> int:
password = read_password(args.password)
ensure_password(password)
if not args.hash.startswith(("$2a$", "$2b$", "$2y$")):
raise ValueError("Malformed bcrypt hash string.")
ok = bcrypt.checkpw(password.encode("utf-8"), args.hash.encode("utf-8"))
out = f"verification: {'VALID' if ok else 'INVALID'}\n"
if args.output:
Path(args.output).write_text(out, encoding="utf-8")
print(out, end="")
return 0 if ok else 2
def cmd_benchmark(args: argparse.Namespace) -> int:
max_cost = parse_cost(args.max_cost)
password = args.password or "BenchmarkSamplePassword!"
lines = ["cost,time_ms"]
for c in range(8, max_cost + 1):
_, ms = hash_one(password, c)
lines.append(f"{c},{ms:.2f}")
out = "\n".join(lines) + "\n"
if args.output:
Path(args.output).write_text(out, encoding="utf-8")
print(out, end="")
return 0
def old_hash_matches(password: str, old_hash: str, hash_type: str) -> bool:
ht = hash_type.lower()
if ht == "md5":
return hashlib.md5(password.encode("utf-8")).hexdigest() == old_hash.lower()
if ht == "sha256":
return hashlib.sha256(password.encode("utf-8")).hexdigest() == old_hash.lower()
raise ValueError(f"Unsupported hash type: {hash_type}")
def cmd_migrate(args: argparse.Namespace) -> int:
cost = parse_cost(args.cost)
ensure_password(args.password)
in_path = Path(args.file)
rows_out = [("username", "bcrypt_hash", "status")]
with in_path.open("r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
username = row.get("username", "")
old_hash = row.get("old_hash", "")
hash_type = row.get("hash_type", "")
try:
if not old_hash_matches(args.password, old_hash, hash_type):
rows_out.append((username, "", "old_hash_mismatch"))
else:
new_hash, _ = hash_one(args.password, cost)
rows_out.append((username, new_hash, "migrated"))
except Exception as exc:
rows_out.append((username, "", str(exc).replace(",", ";")))
out = "\n".join([",".join(r) for r in rows_out]) + "\n"
if args.output:
Path(args.output).write_text(out, encoding="utf-8")
print(out, end="")
return 0
def demo() -> int:
password = "S@mpl3P@ssw0rd!"
for c in (10, 12, 14):
h, ms = hash_one(password, c)
ok = bcrypt.checkpw(password.encode("utf-8"), h.encode("utf-8"))
print(f"cost={c} hash={h} time_ms={ms:.2f} verify={'VALID' if ok else 'INVALID'}")
wrong = bcrypt.checkpw("wrong-password".encode("utf-8"), h.encode("utf-8"))
print(f"wrong verification: {'VALID' if wrong else 'INVALID'}")
return cmd_benchmark(argparse.Namespace(max_cost=12, password=password, output=None))
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(description="Bcrypt Password Hasher")
sub = p.add_subparsers(dest="command")
h = sub.add_parser("hash")
h.add_argument("password", nargs="?")
h.add_argument("--cost", type=int, default=12)
h.add_argument("--file")
h.add_argument("--output")
v = sub.add_parser("verify")
v.add_argument("password", nargs="?")
v.add_argument("hash")
v.add_argument("--output")
b = sub.add_parser("benchmark")
b.add_argument("--max-cost", type=int, default=14)
b.add_argument("--password")
b.add_argument("--output")
m = sub.add_parser("migrate")
m.add_argument("--file", required=True)
m.add_argument("--password", required=True)
m.add_argument("--cost", type=int, default=12)
m.add_argument("--output")
return p
def main() -> int:
parser = build_parser()
args = parser.parse_args()
if not args.command:
return demo()
if args.command == "hash":
return cmd_hash(args)
if args.command == "verify":
return cmd_verify(args)
if args.command == "benchmark":
return cmd_benchmark(args)
if args.command == "migrate":
return cmd_migrate(args)
raise ValueError("Unknown subcommand")
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc:
print(f"Error: {exc}", file=sys.stderr)
raise SystemExit(1)