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

RSA Digital Signature Tool (python, written by Codex)

envgap__codex__python-t1-12

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

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 #12 · 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: RSA Digital Signature Tool

Write a program that generates RSA key pairs and uses them to create and verify digital signatures on files, ensuring document authenticity and integrity.

FUNCTIONAL REQUIREMENTS:
- Support three subcommands: keygen, sign, and verify
- keygen: Generate an RSA key pair (2048 or 4096 bit, selectable via --bits flag, default 2048) and save the private key and public key as separate PEM files
- sign: Accept a file path and private key path, compute a SHA-256 hash of the file, sign it with RSA-PSS padding, and save the signature as a separate .sig file
- verify: Accept a file path, signature file path, and public key path, then verify the signature and print whether it is valid or invalid with details
- Support signing multiple files at once by accepting a directory path via --batch flag
- Support a --output flag to specify where to save generated keys or signatures
- Display key information: key size, fingerprint (SHA-256 hash of public key), and creation timestamp
- Support exporting the public key in both PEM and DER formats via --format flag
- Print detailed results to console: for signing show the file hash and signature size, for verification show match/mismatch status and key details
- If no arguments are given, generate a sample key pair, create a sample text file, sign it, verify the signature, then tamper with the file and show that verification fails
- Handle errors gracefully: invalid key format, mismatched key/signature, corrupted files, unsupported key sizes

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
# RSA Digital Signature Tool (Python)

Implements `keygen`, `sign`, and `verify` using RSA-PSS and SHA-256.

## Requirements

- Ubuntu 22.04
- Python 3.10+

## Dependencies

- Direct:
  - `cryptography==44.0.0`
- Transitive (pinned):
  - `cffi==1.17.1`
  - `pycparser==2.22`

## Setup

```bash
python -m pip install -r requirements.txt
```

## Usage

```bash
python src/main.py keygen [--bits 2048|4096] [--output dir] [--format pem|der|both]
python src/main.py sign <file-or-dir> <private-key.pem> [--batch] [--output file-or-dir]
python src/main.py verify <file> <signature.sig> <public-key.pem>
```

No args runs an end-to-end demo including tamper detection.
requirements.txt
cryptography==44.0.0
cffi==1.17.1
pycparser==2.22
src/main.py
#!/usr/bin/env python3
import argparse
import hashlib
from datetime import datetime, timezone
from pathlib import Path
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key


def fingerprint_public_key(public_key) -> str:
    der = public_key.public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo)
    return hashlib.sha256(der).hexdigest()


def key_info(public_key) -> dict:
    return {
        "key_size": public_key.key_size,
        "fingerprint_sha256": fingerprint_public_key(public_key),
        "created_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
    }


def keygen(bits: int, output_dir: Path, fmt: str) -> None:
    if bits not in (2048, 4096):
        raise ValueError("Unsupported key size. Use 2048 or 4096.")
    if fmt not in {"pem", "der", "both"}:
        raise ValueError("Unsupported --format. Use pem|der|both.")
    output_dir.mkdir(parents=True, exist_ok=True)

    private_key = rsa.generate_private_key(public_exponent=65537, key_size=bits)
    public_key = private_key.public_key()

    priv_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )
    pub_pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo,
    )
    pub_der = public_key.public_bytes(
        encoding=serialization.Encoding.DER,
        format=serialization.PublicFormat.SubjectPublicKeyInfo,
    )

    private_path = output_dir / "private_key.pem"
    private_path.write_bytes(priv_pem)
    if fmt in {"pem", "both"}:
        (output_dir / "public_key.pem").write_bytes(pub_pem)
    if fmt in {"der", "both"}:
        (output_dir / "public_key.der").write_bytes(pub_der)

    info = key_info(public_key)
    print("Key generation complete")
    print(f"  Private key : {private_path}")
    if fmt in {"pem", "both"}:
        print(f"  Public PEM  : {output_dir / 'public_key.pem'}")
    if fmt in {"der", "both"}:
        print(f"  Public DER  : {output_dir / 'public_key.der'}")
    print(f"  Key size    : {info['key_size']}")
    print(f"  Fingerprint : {info['fingerprint_sha256']}")
    print(f"  Created at  : {info['created_at']}")


def sign_file(file_path: Path, private_key, output_path: Path) -> None:
    data = file_path.read_bytes()
    file_hash = hashlib.sha256(data).hexdigest()
    signature = private_key.sign(
        data,
        padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32),
        hashes.SHA256(),
    )
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_bytes(signature)
    print(f"Signed: {file_path}")
    print(f"  SHA-256 hash  : {file_hash}")
    print(f"  Signature size: {len(signature)} bytes")
    print(f"  Signature file: {output_path}")


def sign_command(input_path: Path, private_key_path: Path, batch: bool, output: Path | None) -> None:
    private_key = load_pem_private_key(private_key_path.read_bytes(), password=None)
    if batch:
        if not input_path.is_dir():
            raise ValueError("--batch requires a directory path.")
        out_dir = output if output else Path("signatures").resolve()
        for f in input_path.rglob("*"):
            if not f.is_file():
                continue
            rel = f.relative_to(input_path)
            sig_path = out_dir / (str(rel) + ".sig")
            sign_file(f, private_key, sig_path)
    else:
        sig_path = output if output else Path(str(input_path) + ".sig")
        sign_file(input_path, private_key, sig_path)


def verify_file(file_path: Path, sig_path: Path, public_key) -> bool:
    data = file_path.read_bytes()
    signature = sig_path.read_bytes()
    file_hash = hashlib.sha256(data).hexdigest()
    ok = True
    try:
        public_key.verify(
            signature,
            data,
            padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32),
            hashes.SHA256(),
        )
    except Exception:
        ok = False
    info = key_info(public_key)
    print(f"Verify: {file_path}")
    print(f"  SHA-256 hash  : {file_hash}")
    print(f"  Signature file: {sig_path}")
    print(f"  Status        : {'VALID' if ok else 'INVALID'}")
    print(f"  Key size      : {info['key_size']}")
    print(f"  Fingerprint   : {info['fingerprint_sha256']}")
    return ok


def verify_command(file_path: Path, sig_path: Path, public_key_path: Path) -> bool:
    public_key = load_pem_public_key(public_key_path.read_bytes())
    return verify_file(file_path, sig_path, public_key)


def run_demo() -> None:
    root = Path("rsa_sample").resolve()
    root.mkdir(parents=True, exist_ok=True)
    sample_file = root / "sample.txt"
    sample_file.write_text("RSA digital signature demo.\n", encoding="utf-8")
    keygen(2048, root, "both")
    private_key_path = root / "private_key.pem"
    public_key_path = root / "public_key.pem"
    sig_path = root / "sample.txt.sig"
    sign_command(sample_file, private_key_path, False, sig_path)
    valid1 = verify_command(sample_file, sig_path, public_key_path)
    sample_file.write_text(sample_file.read_text(encoding="utf-8") + "tampered\n", encoding="utf-8")
    valid2 = verify_command(sample_file, sig_path, public_key_path)
    print(f"Demo result: initial verify={valid1}, after tamper verify={valid2}")


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="RSA Digital Signature Tool")
    sub = parser.add_subparsers(dest="command")

    k = sub.add_parser("keygen")
    k.add_argument("--bits", type=int, default=2048)
    k.add_argument("--output", default=".")
    k.add_argument("--format", default="pem")

    s = sub.add_parser("sign")
    s.add_argument("input_path")
    s.add_argument("private_key_path")
    s.add_argument("--batch", action="store_true")
    s.add_argument("--output")

    v = sub.add_parser("verify")
    v.add_argument("file_path")
    v.add_argument("signature_path")
    v.add_argument("public_key_path")
    return parser


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()
    if not args.command:
        run_demo()
        return 0

    try:
        if args.command == "keygen":
            keygen(args.bits, Path(args.output).resolve(), args.format.lower())
        elif args.command == "sign":
            sign_command(
                Path(args.input_path).resolve(),
                Path(args.private_key_path).resolve(),
                args.batch,
                Path(args.output).resolve() if args.output else None,
            )
        elif args.command == "verify":
            ok = verify_command(
                Path(args.file_path).resolve(),
                Path(args.signature_path).resolve(),
                Path(args.public_key_path).resolve(),
            )
            if not ok:
                return 2
        else:
            parser.print_help()
            return 1
        return 0
    except FileNotFoundError as exc:
        print(f"Error: file not found: {exc}")
        return 1
    except PermissionError:
        print("Error: permission denied.")
        return 1
    except ValueError as exc:
        print(f"Error: {exc}")
        return 1
    except Exception as exc:
        print(f"Error: {exc}")
        return 1


if __name__ == "__main__":
    raise SystemExit(main())