← All tasks
pythoncodex/python-t1 #17Not a task: already works

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)