RSA Digital Signature Tool (java, written by Codex)
envgap__codex__java-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
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/java-t1 #12 · 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: 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 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tmlr.codex_generated.p12</groupId>
<artifactId>rsa-digital-signature-tool</artifactId>
<version>1.0.0</version>
<name>RSA Digital Signature Tool</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<mainClass>RsaDigitalSignatureTool</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# RSA Digital Signature Tool (Java) Implements RSA key generation, signing, and verification with RSA-PSS/SHA-256. ## Requirements - Ubuntu 22.04 - JDK 17+ - Maven 3.9+ ## Dependencies - Direct: none (JDK crypto APIs) - Transitive: none Build plugins are pinned in `pom.xml`. ## Build ```bash mvn -q -DskipTests compile ``` ## Usage ```bash mvn -q exec:java -Dexec.args="keygen [--bits 2048|4096] [--output dir] [--format pem|der|both]" mvn -q exec:java -Dexec.args="sign <file-or-dir> <private-key.pem> [--batch] [--output file-or-dir]" mvn -q exec:java -Dexec.args="verify <file> <signature.sig> <public-key.pem>" ``` No args runs a full demo including tamper detection.
src/main/java/RsaDigitalSignatureTool.java
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.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.PSSParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class RsaDigitalSignatureTool {
private static final PSSParameterSpec PSS_SHA256 = new PSSParameterSpec(
"SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1
);
private record ParsedArgs(Map<String, String> options, List<String> positional) {}
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 "keygen" -> runKeygen(parsed);
case "sign" -> runSign(parsed);
case "verify" -> runVerify(parsed);
default -> throw new IllegalArgumentException("Unknown subcommand. Use keygen, sign, verify.");
}
} 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 token = args[i];
if (token.startsWith("--")) {
String key = token.substring(2);
if (i + 1 < args.length && !args[i + 1].startsWith("--")) options.put(key, args[++i]);
else options.put(key, "true");
} else {
positional.add(token);
}
}
return new ParsedArgs(options, positional);
}
private static String toPem(String type, byte[] data) {
String b64 = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8)).encodeToString(data);
return "-----BEGIN " + type + "-----\n" + b64 + "\n-----END " + type + "-----\n";
}
private static byte[] fromPem(String pem) {
String s = pem.replaceAll("-----BEGIN [^-]+-----", "")
.replaceAll("-----END [^-]+-----", "")
.replaceAll("\\s", "");
return Base64.getDecoder().decode(s);
}
private static String sha256Hex(byte[] data) throws Exception {
byte[] hash = MessageDigest.getInstance("SHA-256").digest(data);
StringBuilder sb = new StringBuilder();
for (byte b : hash) sb.append(String.format("%02x", b));
return sb.toString();
}
private static int publicKeyBits(PublicKey publicKey) {
if (publicKey instanceof RSAPublicKey rsa) return rsa.getModulus().bitLength();
return -1;
}
private static void runKeygen(ParsedArgs parsed) throws Exception {
int bits = Integer.parseInt(parsed.options.getOrDefault("bits", "2048"));
if (bits != 2048 && bits != 4096) throw new IllegalArgumentException("Unsupported key size. Use 2048 or 4096.");
String format = parsed.options.getOrDefault("format", "pem").toLowerCase();
if (!List.of("pem", "der", "both").contains(format)) throw new IllegalArgumentException("Unsupported --format. Use pem|der|both.");
Path outDir = Paths.get(parsed.options.getOrDefault("output", ".")).toAbsolutePath();
Files.createDirectories(outDir);
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(bits);
KeyPair kp = gen.generateKeyPair();
byte[] privDer = kp.getPrivate().getEncoded();
byte[] pubDer = kp.getPublic().getEncoded();
Path privatePath = outDir.resolve("private_key.pem");
Files.writeString(privatePath, toPem("PRIVATE KEY", privDer), StandardCharsets.UTF_8);
if (format.equals("pem") || format.equals("both")) {
Files.writeString(outDir.resolve("public_key.pem"), toPem("PUBLIC KEY", pubDer), StandardCharsets.UTF_8);
}
if (format.equals("der") || format.equals("both")) {
Files.write(outDir.resolve("public_key.der"), pubDer);
}
System.out.println("Key generation complete");
System.out.println(" Private key : " + privatePath);
if (format.equals("pem") || format.equals("both")) System.out.println(" Public PEM : " + outDir.resolve("public_key.pem"));
if (format.equals("der") || format.equals("both")) System.out.println(" Public DER : " + outDir.resolve("public_key.der"));
System.out.println(" Key size : " + publicKeyBits(kp.getPublic()));
System.out.println(" Fingerprint : " + sha256Hex(pubDer));
System.out.println(" Created at : " + Instant.now());
}
private static PrivateKey loadPrivateKey(Path path) throws Exception {
String pem = Files.readString(path, StandardCharsets.UTF_8);
byte[] der = fromPem(pem);
return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(der));
}
private static PublicKey loadPublicKey(Path path) throws Exception {
if (path.toString().toLowerCase().endsWith(".der")) {
byte[] der = Files.readAllBytes(path);
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(der));
}
String pem = Files.readString(path, StandardCharsets.UTF_8);
byte[] der = fromPem(pem);
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(der));
}
private static byte[] signBytes(byte[] data, PrivateKey privateKey) throws Exception {
Signature sig = Signature.getInstance("RSASSA-PSS");
sig.setParameter(PSS_SHA256);
sig.initSign(privateKey);
sig.update(data);
return sig.sign();
}
private static boolean verifyBytes(byte[] data, byte[] signature, PublicKey publicKey) throws Exception {
Signature sig = Signature.getInstance("RSASSA-PSS");
sig.setParameter(PSS_SHA256);
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
}
private static void signOne(Path file, PrivateKey privateKey, Path signatureOut) throws Exception {
byte[] data = Files.readAllBytes(file);
byte[] hash = MessageDigest.getInstance("SHA-256").digest(data);
byte[] sig = signBytes(data, privateKey);
Files.createDirectories(signatureOut.getParent());
Files.write(signatureOut, sig);
System.out.println("Signed: " + file);
System.out.println(" File hash : " + sha256Hex(data));
System.out.println(" Signature size : " + sig.length + " bytes");
System.out.println(" Signature file : " + signatureOut);
}
private static void runSign(ParsedArgs parsed) throws Exception {
if (parsed.positional.size() < 3) throw new IllegalArgumentException("Usage: sign <file-or-dir> <private-key-path> [--batch]");
Path input = Paths.get(parsed.positional.get(1)).toAbsolutePath();
Path privateKeyPath = Paths.get(parsed.positional.get(2)).toAbsolutePath();
PrivateKey privateKey = loadPrivateKey(privateKeyPath);
Path output = parsed.options.containsKey("output") ? Paths.get(parsed.options.get("output")).toAbsolutePath() : null;
boolean batch = parsed.options.containsKey("batch");
if (batch) {
if (!Files.isDirectory(input)) throw new IllegalArgumentException("--batch requires a directory input.");
Path outDir = output != null ? output : Paths.get("signatures").toAbsolutePath();
try (Stream<Path> st = Files.walk(input)) {
for (Path f : st.filter(Files::isRegularFile).toList()) {
Path rel = input.relativize(f);
Path sigOut = outDir.resolve(rel.toString() + ".sig");
signOne(f, privateKey, sigOut);
}
}
} else {
Path sigOut = output != null ? output : Paths.get(input + ".sig");
signOne(input, privateKey, sigOut);
}
}
private static boolean verifyOne(Path file, Path signaturePath, PublicKey publicKey) throws Exception {
byte[] data = Files.readAllBytes(file);
byte[] sig = Files.readAllBytes(signaturePath);
boolean ok = verifyBytes(data, sig, publicKey);
System.out.println("Verify: " + file);
System.out.println(" Signature file: " + signaturePath);
System.out.println(" Status : " + (ok ? "VALID" : "INVALID"));
System.out.println(" Key size : " + publicKeyBits(publicKey));
System.out.println(" Fingerprint : " + sha256Hex(publicKey.getEncoded()));
return ok;
}
private static void runVerify(ParsedArgs parsed) throws Exception {
if (parsed.positional.size() < 4) throw new IllegalArgumentException("Usage: verify <file> <signature> <public-key>");
Path file = Paths.get(parsed.positional.get(1)).toAbsolutePath();
Path sig = Paths.get(parsed.positional.get(2)).toAbsolutePath();
Path pub = Paths.get(parsed.positional.get(3)).toAbsolutePath();
PublicKey publicKey = loadPublicKey(pub);
boolean ok = verifyOne(file, sig, publicKey);
if (!ok) System.exit(2);
}
private static void runDemo() throws Exception {
Path root = Paths.get("rsa_sample").toAbsolutePath();
Files.createDirectories(root);
Path sampleFile = root.resolve("sample.txt");
Files.writeString(sampleFile, "RSA digital signature demo.\n", StandardCharsets.UTF_8);
runKeygen(new ParsedArgs(Map.of("bits", "2048", "output", root.toString(), "format", "both"), List.of("keygen")));
Path priv = root.resolve("private_key.pem");
Path pub = root.resolve("public_key.pem");
Path sig = root.resolve("sample.txt.sig");
runSign(new ParsedArgs(Map.of("output", sig.toString()), List.of("sign", sampleFile.toString(), priv.toString())));
boolean valid1 = verifyOne(sampleFile, sig, loadPublicKey(pub));
Files.writeString(sampleFile, Files.readString(sampleFile, StandardCharsets.UTF_8) + "tampered\n", StandardCharsets.UTF_8);
boolean valid2 = verifyOne(sampleFile, sig, loadPublicKey(pub));
System.out.println("Demo result: initial verify=" + valid1 + ", after tamper verify=" + valid2);
}
}