AES-256 File Encryption Tool (java, written by Codex)
envgap__codex__java-t1-11
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 #11 · 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: AES-256 File Encryption Tool Write a program that encrypts and decrypts files using AES-256 encryption in CBC mode with proper key derivation, initialization vectors, and authenticated encryption to prevent tampering. FUNCTIONAL REQUIREMENTS: - Accept a file path, a password, and a mode (encrypt or decrypt) as command-line arguments - Derive the encryption key from the password using PBKDF2 with SHA-256, a random 16-byte salt, and at least 100,000 iterations - Encrypt using AES-256-CBC with a random 16-byte initialization vector (IV) for each encryption - Prepend the salt and IV to the encrypted output file so they are available for decryption - Add HMAC-SHA256 authentication tag to the encrypted file to detect tampering during decryption - During decryption, verify the HMAC before attempting to decrypt and report a clear error if the file has been modified - Support encrypting entire directories recursively via --recursive flag, preserving directory structure in the output - Support a --output flag to specify the output file or directory (default: append .enc for encryption, strip .enc for decryption) - Display progress information for large files: file size, percentage complete, and throughput - If no input file is given, generate a sample text file with random content, encrypt it, then decrypt it back and verify the output matches the original - Handle errors gracefully: wrong password (HMAC mismatch), corrupted file, file not found, permission denied 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.p11</groupId>
<artifactId>aes256-file-encryption-tool</artifactId>
<version>1.0.0</version>
<name>AES-256 File Encryption 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>Aes256FileEncryptionTool</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# AES-256 File Encryption Tool (Java) AES-256-CBC file encryption/decryption with PBKDF2 key derivation and HMAC authentication. ## Requirements - Ubuntu 22.04 - JDK 17+ - Maven 3.9+ ## Dependencies - Direct: none (JDK crypto APIs) - Transitive: none Build plugin versions are pinned in `pom.xml`. ## Build ```bash mvn -q -DskipTests compile ``` ## Usage ```bash mvn -q exec:java -Dexec.args="<input-path> <password> <encrypt|decrypt> [--recursive] [--output <path>]" ``` Examples: ```bash mvn -q exec:java -Dexec.args="./secret.txt myPassword encrypt" mvn -q exec:java -Dexec.args="./secret.txt.enc myPassword decrypt" mvn -q exec:java -Dexec.args="./data myPassword encrypt --recursive --output ./encrypted_data" ``` No arguments runs a self-test.
src/main/java/Aes256FileEncryptionTool.java
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
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.security.SecureRandom;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class Aes256FileEncryptionTool {
private static final byte[] MAGIC = "A2CB".getBytes(StandardCharsets.UTF_8);
private static final int SALT_LEN = 16;
private static final int IV_LEN = 16;
private static final int HMAC_LEN = 32;
private static final int PBKDF2_ITERS = 120000;
private static final int CHUNK_SIZE = 1024 * 1024;
private static final SecureRandom RNG = new SecureRandom();
private record ParsedArgs(Map<String, String> options, List<String> positional) {}
private record Keys(byte[] encKey, byte[] macKey) {}
public static void main(String[] args) {
try {
ParsedArgs parsed = parseArgs(args);
if (parsed.positional.size() < 3) {
runSelfTest("sample-password-123");
return;
}
Path inputPath = Paths.get(parsed.positional.get(0)).toAbsolutePath();
String password = parsed.positional.get(1);
String mode = parsed.positional.get(2).toLowerCase();
if (!mode.equals("encrypt") && !mode.equals("decrypt")) throw new IllegalArgumentException("Mode must be encrypt or decrypt.");
if (!Files.exists(inputPath)) throw new IOException("File not found: " + inputPath);
Path output = parsed.options.containsKey("output") ? Paths.get(parsed.options.get("output")).toAbsolutePath() : null;
boolean recursive = parsed.options.containsKey("recursive");
processPath(inputPath, output, password, mode, recursive);
} 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 Keys deriveKeys(String password, byte[] salt) throws Exception {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERS, 512);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] km = skf.generateSecret(spec).getEncoded();
return new Keys(Arrays.copyOfRange(km, 0, 32), Arrays.copyOfRange(km, 32, 64));
}
private static void printProgress(String label, long done, long total, long startNano) {
double elapsed = (System.nanoTime() - startNano) / 1_000_000_000.0;
double pct = total == 0 ? 100.0 : ((double) done / total) * 100.0;
double throughput = elapsed <= 0 ? 0.0 : done / elapsed / (1024.0 * 1024.0);
System.out.printf("\r%s | %d bytes | %5.1f%% | %6.2f MiB/s", label, total, pct, throughput);
if (done >= total) System.out.println();
}
private static byte[] encryptBytes(byte[] input, String password, String label) throws Exception {
byte[] salt = new byte[SALT_LEN];
byte[] iv = new byte[IV_LEN];
RNG.nextBytes(salt);
RNG.nextBytes(iv);
Keys keys = deriveKeys(password, salt);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey encKey = new SecretKeySpec(keys.encKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, encKey, new IvParameterSpec(iv));
ByteArrayOutputStream ct = new ByteArrayOutputStream();
long start = System.nanoTime();
int processed = 0;
for (int i = 0; i < input.length; i += CHUNK_SIZE) {
int n = Math.min(CHUNK_SIZE, input.length - i);
byte[] chunk = cipher.update(input, i, n);
if (chunk != null) ct.write(chunk);
processed += n;
printProgress(label, processed, input.length, start);
}
ct.write(cipher.doFinal());
byte[] ciphertext = ct.toByteArray();
byte[] header = concat(MAGIC, salt, iv);
byte[] tag = hmacSha256(keys.macKey, concat(header, ciphertext));
return concat(header, ciphertext, tag);
}
private static byte[] decryptBytes(byte[] input, String password, String label) throws Exception {
if (input.length < MAGIC.length + SALT_LEN + IV_LEN + HMAC_LEN) throw new IllegalArgumentException("Corrupted file: too short.");
if (!Arrays.equals(Arrays.copyOfRange(input, 0, MAGIC.length), MAGIC)) throw new IllegalArgumentException("Corrupted file: invalid header.");
int saltStart = MAGIC.length;
int ivStart = saltStart + SALT_LEN;
int bodyStart = ivStart + IV_LEN;
int tagStart = input.length - HMAC_LEN;
byte[] salt = Arrays.copyOfRange(input, saltStart, ivStart);
byte[] iv = Arrays.copyOfRange(input, ivStart, bodyStart);
byte[] ciphertext = Arrays.copyOfRange(input, bodyStart, tagStart);
byte[] storedTag = Arrays.copyOfRange(input, tagStart, input.length);
Keys keys = deriveKeys(password, salt);
byte[] calcTag = hmacSha256(keys.macKey, Arrays.copyOfRange(input, 0, tagStart));
if (!MessageDigest.isEqual(storedTag, calcTag)) {
throw new IllegalArgumentException("HMAC mismatch: wrong password or file has been modified.");
}
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey encKey = new SecretKeySpec(keys.encKey, "AES");
cipher.init(Cipher.DECRYPT_MODE, encKey, new IvParameterSpec(iv));
ByteArrayOutputStream out = new ByteArrayOutputStream();
long start = System.nanoTime();
int processed = 0;
for (int i = 0; i < ciphertext.length; i += CHUNK_SIZE) {
int n = Math.min(CHUNK_SIZE, ciphertext.length - i);
byte[] chunk = cipher.update(ciphertext, i, n);
if (chunk != null) out.write(chunk);
processed += n;
printProgress(label, processed, ciphertext.length, start);
}
out.write(cipher.doFinal());
return out.toByteArray();
}
private static byte[] hmacSha256(byte[] key, byte[] message) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(message);
}
private static byte[] concat(byte[]... parts) {
int total = 0;
for (byte[] p : parts) total += p.length;
byte[] out = new byte[total];
int pos = 0;
for (byte[] p : parts) {
System.arraycopy(p, 0, out, pos, p.length);
pos += p.length;
}
return out;
}
private static Path defaultOutput(Path input, String mode) {
String s = input.toString();
if (mode.equals("encrypt")) return Paths.get(s + ".enc");
if (s.endsWith(".enc")) return Paths.get(s.substring(0, s.length() - 4));
return Paths.get(s + ".dec");
}
private static void processFile(Path input, Path output, String password, String mode) throws Exception {
byte[] data = Files.readAllBytes(input);
byte[] out = mode.equals("encrypt")
? encryptBytes(data, password, "Encrypting " + input.getFileName())
: decryptBytes(data, password, "Decrypting " + input.getFileName());
Files.createDirectories(output.getParent());
Files.write(output, out);
}
private static List<Path> collectFiles(Path dir) throws IOException {
try (Stream<Path> st = Files.walk(dir)) {
return st.filter(Files::isRegularFile).toList();
}
}
private static void processPath(Path input, Path output, String password, String mode, boolean recursive) throws Exception {
if (Files.isRegularFile(input)) {
Path outFile = output != null ? output : defaultOutput(input, mode);
processFile(input, outFile, password, mode);
System.out.println(mode + " complete: " + input + " -> " + outFile);
return;
}
if (!Files.isDirectory(input)) throw new IllegalArgumentException("Input path must be file or directory.");
if (!recursive) throw new IllegalArgumentException("Input is a directory. Use --recursive.");
Path outRoot = output != null ? output : Paths.get(input + "_" + mode + "ed");
for (Path src : collectFiles(input)) {
Path rel = input.relativize(src);
Path target = defaultOutput(outRoot.resolve(rel), mode);
processFile(src, target, password, mode);
System.out.println(mode + " complete: " + src + " -> " + target);
}
}
private static String randomText(int bytes) {
byte[] buf = new byte[bytes];
RNG.nextBytes(buf);
StringBuilder out = new StringBuilder(bytes * 2);
for (byte b : buf) out.append(String.format("%02x", b));
return out.toString();
}
private static void runSelfTest(String password) throws Exception {
Path sample = Paths.get("sample_plain.txt").toAbsolutePath();
Path enc = Paths.get("sample_plain.txt.enc").toAbsolutePath();
Path dec = Paths.get("sample_plain.txt.dec").toAbsolutePath();
Files.writeString(sample, randomText(4096), StandardCharsets.UTF_8);
processFile(sample, enc, password, "encrypt");
processFile(enc, dec, password, "decrypt");
if (!Arrays.equals(Files.readAllBytes(sample), Files.readAllBytes(dec))) {
throw new IllegalStateException("Self-test failed: decrypted output mismatch.");
}
System.out.println("Self-test successful:");
System.out.println(" " + sample);
System.out.println(" " + enc);
System.out.println(" " + dec);
System.out.println(" " + Instant.now());
}
}