← All tasks
javacodex/java-t1 #11Not a task: already works

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());
    }
}