Build a DES Encryption Lab in Java

Generate DES keys, encrypt files, and run cryptography experiments in Java.

Introduction

30 Second Summary

Every time you send a password, make a payment, or open a banking app, encryption scrambles your data so only the right person can read it. The algorithm doing that work is built on the same principles you are about to explore.

In this project, you will build a DES encryption lab in Java that generates secret keys, encrypts files of varying sizes, and runs experiments to test encryption's real-world properties. Your program will produce timing data, compression comparisons, and a transfer simulation you can reference directly in your written answers.

What You'll Build

Run your program and see a formatted table of encryption times across five file sizes, watch decryption fail with the wrong key, and prove that encrypted data cannot be compressed.

By the end of this project, you'll have:

  • A working DES encryption program that generates secret keys, encrypts files from 1 KB to 1 MB, and decrypts them back to the original.
  • A formatted timing and throughput table showing how encryption performance scales across five file sizes, measured in bits per second.
  • Experimental proof that wrong-key decryption fails, that encrypted data resists compression, and that a key transfer simulation works across separate program runs.
  • Secret Mission: Add a command-line interface so your app works as a real encrypt/decrypt tool with keygen, encrypt, and decrypt modes.

Are there any prerequisites?

None. This guide walks you through installing everything you need, including JDK 21 and the VS Code extensions for Java development.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters for your assignment.

Set Up Your Java Development Environment

Your encryption lab is a Java program. To compile and run Java code, you need the Java Development Kit (JDK) installed on your machine.

In this step, you'll install JDK 21 and configure VS Code to recognize Java files. You'll also create the project folder and starter file for the rest of the project.

In this step, get ready to:
  • Install JDK 21 and verify it in the terminal.
  • Install the Extension Pack for Java in VS Code.
  • Create the des-lab project folder and DESEncryptor.java file.
Check and install JDK 21

The JDK is the toolkit that includes both the Java compiler (javac) and the Java runtime (java). This project uses JDK 21, which is the previous Long-Term Support release of Java.

  • In VS Code, click Terminal in the top menu bar.
  • Click New Terminal.
  • Check if Java is already installed by running this command:
java -version

✔️ I see version 21 or higher

JDK 21 is already installed. Your Omarchy environment is ready for the next section.

ⓧ I see an older version

Omarchy can keep several Java versions side by side. You need to install OpenJDK 21 and make it the active version for this project.

  • Install OpenJDK 21 from Omarchy's package sources by running this command:
omarchy pkg add jdk21-openjdk

Omarchy may ask for your password and confirmation before it installs the package. The download can take a few minutes.

  • Set OpenJDK 21 as the active Java environment by running this command:
sudo archlinux-java set java-21-openjdk
  • Reload the environment settings by running this command:
source /etc/profile
  • Check the active Java version by running this command:
java -version

You should now see version 21 in the output.

Still seeing the older version?

Open a new terminal and run the version check again. If version 21 still does not appear, check which Java environment is active with archlinux-java status.

Help me switch Omarchy to OpenJDK 21.

ⓧ Command not found

Java is not installed yet. Omarchy can install OpenJDK 21 directly from the official Arch package repository.

  • Install OpenJDK 21 by running this command:
omarchy pkg add jdk21-openjdk

Omarchy may ask for your password and confirmation before it installs the package. The download can take a few minutes.

  • Reload the environment settings by running this command:
source /etc/profile
  • Check the installed Java version by running this command:
java -version

You should now see version 21 in the output.

Java command still missing?

Close the current terminal and open a new one so Omarchy reloads the Java paths. Then run the version check again.

Help me find why Java is still unavailable on Omarchy.

  • Confirm the Java compiler is also available by running this command:
javac -version

You should see output starting with javac 21 followed by a patch number.

javac not found but java works?

This usually means a Java Runtime Environment (JRE) is installed instead of the full JDK. Go back to the Oracle download page and make sure you downloaded from the JDK 21 section, not a standalone JRE.

Still stuck? Help me figure out why javac is not found even though java -version works. I installed JDK 21 on my machine.

Install the Extension Pack for Java

VS Code does not understand Java out of the box. The Extension Pack for Java adds language support, debugging, and project tools so VS Code can compile and run your code.

  • Open the Extensions panel by pressing Ctrl+Shift+X.
  • Enter Extension Pack for Java in the search bar at the top of the panel.
  • Select the result published by Microsoft.
  • Click Install.

Once the install finishes, you should see a green checkmark or Disable button where Install used to be. That confirms the extension is active.

What does this extension pack include?

The Extension Pack for Java bundles six extensions into one install. The most important ones for this project are Language Support for Java (syntax highlighting, code completion) and Debugger for Java (run and debug from VS Code).

Create your project folder and starter file

Every file you write in this project lives inside a single folder called des-lab. Creating it on the Desktop keeps it easy to find.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create the project folder by running this command:
mkdir des-lab
  • Click File in the VS Code menu bar.
  • Click Open Folder.
  • Select Desktop in the file picker.
  • Select the des-lab folder.
  • Click Open.

VS Code reloads with des-lab as the workspace. The Explorer panel on the left shows the empty folder.

  • In the Explorer panel, hover over the DES-LAB heading and click the New File icon (a page with a plus sign).
  • Type DESEncryptor.java and press Enter.
  • Confirm the file exists by opening a new terminal (Terminal > New Terminal) and running:
ls

You should see DESEncryptor.java listed in the output. The file is empty for now. You'll fill it in the next step.

Don't see the file?

Make sure you opened the des-lab folder (not your Desktop) in VS Code. The terminal prompt should show des-lab as the current directory.

Also check that the filename is exactly DESEncryptor.java with a capital D, E, and S. Java is case-sensitive with filenames.

Need more help? I created DESEncryptor.java in VS Code but ls does not show it. I opened the des-lab folder on my Desktop.

Your Java environment is ready and your project file is waiting. Next up, you'll write your first DES encryption and decryption code.

Build Your First DES Encryptor

In Step 1, you installed JDK 21 and set up VS Code for Java development. Now it's time to write real cryptography code.

Before you can run experiments on file sizes and timing, you need working encryption and decryption methods. In this step, you'll build a baby version of your lab that encrypts a single string and decrypts it back to the original.

In this step, get ready to:
  • Write DES key generation, encryption, and decryption methods in Java.
  • Run your encryptor and see it encrypt and decrypt a message.
  • Add a JDK compatibility safeguard so DES works on any Java build.
Write the core encryption and decryption methods

Symmetric encryption uses a single secret key for both locking and unlocking data. Your lab needs three core methods: one to generate that key, one to encrypt data, and one to decrypt it.

  • Open DESEncryptor.java in VS Code and paste the following code to set up the class, imports, and the key generation method:
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class DESEncryptor {

    // Generate a new random DES secret key
    public static SecretKey generateKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("DES");
        keyGen.init(56);
        return keyGen.generateKey();
    }
}

What does this code do?

The import lines pull in Java's built-in cryptography classes. Some of these imports are for later steps, but adding them all now keeps the file ready to grow.

  • KeyGenerator.getInstance("DES") asks Java's crypto library for a DES key generator.
  • keyGen.init(56) sets the effective key size to 56 bits. A DES key is 64 bits total, but 8 of those bits are parity checks, leaving 56 effective bits.
  • generateKey() returns a random SecretKey object you can use for encryption and decryption.
  • Add the encrypt and decrypt methods inside the DESEncryptor class, right below generateKey and before the closing }:
    // Encrypt data using DES in ECB mode with PKCS5 padding
    public static byte[] encrypt(byte[] data, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    // Decrypt data using the same DES transformation
    public static byte[] decrypt(byte[] data, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(data);
    }

What does this code do?

Both methods use the same cipher transformation: DES/ECB/PKCS5Padding. This tells Java to use the DES algorithm, ECB (Electronic Codebook) mode, and PKCS5 padding.

  • Cipher.ENCRYPT_MODE tells the cipher to scramble plaintext into ciphertext.
  • Cipher.DECRYPT_MODE tells it to reverse the process.
  • doFinal() processes the entire byte array in one pass and returns the result.

💡 Why ECB mode?

ECB is the simplest cipher mode. It encrypts each 8-byte block independently, which makes it easy to understand. Other modes like CBC are more secure because they chain blocks together, but ECB keeps things straightforward for this lab.

  • Save DESEncryptor.java and confirm the code compiles by running this in your terminal:
javac DESEncryptor.java
  • You should see no output, which means the compilation succeeded.

Seeing a compilation error?

  • Make sure the encrypt and decrypt methods are inside the DESEncryptor class (between the opening { after public class DESEncryptor and the final }).
  • Check that each method has matching curly braces.
  • Confirm you are running javac from inside the des-lab folder.

Why won't my DESEncryptor.java compile?

Write the main method and run the program

Your methods are ready, but you need two more pieces before you can run the program. A helper to display raw bytes as readable hex, and a main method that ties everything together.

  • Add the bytesToHex helper method below decrypt, still inside the class:
    // Convert a byte array to a readable hexadecimal string
    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

What does this code do?

Encrypted data is raw bytes that cannot be printed as normal text. bytesToHex converts each byte into a two-character hex value (like 3f or a1), so you can read it in the terminal.

  • Add the main method below bytesToHex, still inside the class:
    public static void main(String[] args) throws Exception {
        // Generate a fresh DES key
        SecretKey key = generateKey();
        String testMessage = "Hello, this is a secret message for DES encryption!";
        // Encrypt the test message
        byte[] encrypted = encrypt(testMessage.getBytes(), key);
        // Decrypt it back
        byte[] decrypted = decrypt(encrypted, key);
        // Display results
        System.out.println("Key (hex): " + bytesToHex(key.getEncoded()));
        System.out.println("Ciphertext (hex): " + bytesToHex(encrypted));
        System.out.println("Decrypted: " + new String(decrypted));
    }

What does this code do?

  • generateKey() creates a random DES key. Every time you run the program, you get a different key.
  • encrypt(testMessage.getBytes(), key) converts the string to bytes and encrypts them with your key.
  • decrypt(encrypted, key) reverses the process, recovering the original bytes.
  • The three println calls display the key, the scrambled ciphertext, and the recovered plaintext so you can confirm the round trip works.

Before you run this, what do you think the Decrypted line will print? Will it match the original test message exactly?

  • Save DESEncryptor.java and compile and run the program with this command:
javac DESEncryptor.java && java DESEncryptor

Your terminal prints three lines. The key appears as a 16-character hex string. The ciphertext is a longer hex string of scrambled data. The decrypted line reads Hello, this is a secret message for DES encryption!, matching the original perfectly.

Program not running?

  • If you see error: class DESEncryptor is public, should be declared in a file named DESEncryptor.java, make sure the filename is exactly DESEncryptor.java with a capital D, E, and S.
  • Check that the main method and bytesToHex method are both inside the DESEncryptor class.
  • Make sure your terminal is in the des-lab folder where DESEncryptor.java lives.

Help me debug my DESEncryptor program

✔️ Awesome, I've got everything!

Great. Double check you've saved DESEncryptor.java and that the decrypted output matches the original test message.

ⓧ I'd like to double check the full code

Scroll up and confirm each of these sections is present in your DESEncryptor.java, in order:

  • 13 import lines at the top of the file.
  • The public class DESEncryptor { declaration.
  • The generateKey method (3 lines inside).
  • The encrypt method (3 lines inside).
  • The decrypt method (3 lines inside).
  • The bytesToHex method (4 lines inside the for loop).
  • The main method with generateKey(), encrypt(), decrypt(), and three println calls.
  • A final closing } for the class.
Add a safety measure for JDK compatibility

DES is an older algorithm that some JDK distributions disable by default for security reasons. Your program worked just now, but on a different machine it might throw an error.

A static initializer block runs automatically when Java loads the class. You'll use one to check whether DES is blocked and re-enable it for this educational lab.

  • In DESEncryptor.java, find this line near the top of the file:
public class DESEncryptor {
  • Directly below that line (before the generateKey method), paste this static initializer block:
    static {
        try {
            // Check if DES is in the disabled algorithms list
            String disabled = Security.getProperty("jdk.crypto.disabledAlgorithms");
            if (disabled != null && disabled.contains("DES")) {
                // Remove DES from disabled list (but keep DESede)
                String updated = disabled.replaceAll("\\bDES(?!ede)\\S*\\s*,?\\s*", "")
                        .replaceAll(",\\s*$", "").trim();
                Security.setProperty("jdk.crypto.disabledAlgorithms", updated);
                System.out.println("[INFO] DES re-enabled for educational purposes.\n");
            }
        } catch (Exception e) {
            System.out.println("[WARN] Could not check disabled algorithms: " + e.getMessage());
        }
    }

What does this code do?

  • Security.getProperty("jdk.crypto.disabledAlgorithms") reads the JDK's list of blocked algorithms.
  • If DES is in that list, the regex removes it while keeping DESede (Triple DES) untouched.
  • Security.setProperty writes the updated list back, unblocking DES for this program only.

On most standard JDK 21 installs, DES is not in the disabled list and the block does nothing. It's a safety net so your lab runs on any Java distribution.

  • Save DESEncryptor.java and recompile and run the program:
javac DESEncryptor.java && java DESEncryptor

You see the same key, ciphertext, and decrypted output as before. If your JDK had DES disabled, you'll also see a [INFO] DES re-enabled for educational purposes. line at the top.

Seeing a compilation error after adding the static block?

  • Make sure the static { } block is inside the class but outside any method. It should sit between the public class DESEncryptor { line and the generateKey method.
  • Check that the try and catch blocks both have matching curly braces.

Why won't my static initializer block compile?

Your first encryptor is running. You can generate a key, scramble a message, and recover the original text. Next up, you'll scale this to encrypt entire files across five different sizes and collect the timing data your assignment needs.

Scale Up to File Experiments

In the last step, you proved that your DES encrypt-decrypt pipeline works on a single string. That is a great first milestone.

Your assignment requires something bigger: timing data across five file sizes, and evidence of what happens when someone tries to decrypt with the wrong key. In this step, you will scale your encryptor from toy strings to real files and capture the throughput numbers you need for your report.

In this step, get ready to:
  • Encrypt and decrypt files across five sizes (1 KB to 1 MB) with timing and throughput data.
  • Attempt decryption with the wrong key and observe the failure.
  • Save encrypted files to disk for use in the next step.
Add the test data generator and experiment setup

To test encryption at different file sizes, you need a way to generate test data of any length. You also need to restructure your program so the experiments live in their own method.

  • In DESEncryptor.java, find the existing main method.
  • Find this code:
    public static void main(String[] args) throws Exception {
        SecretKey key = generateKey();
        String testMessage = "Hello, this is a secret message for DES encryption!";
        byte[] encrypted = encrypt(testMessage.getBytes(), key);
        byte[] decrypted = decrypt(encrypted, key);
        System.out.println("Key (hex): " + bytesToHex(key.getEncoded()));
        System.out.println("Ciphertext (hex): " + bytesToHex(encrypted));
        System.out.println("Decrypted: " + new String(decrypted));
    }
  • Select everything from public static void main through its closing } and delete it.
  • Replace it with this code, which adds a generateTestData method, a new main, and the beginning of a runExperiments method:
    public static byte[] generateTestData(int sizeInBytes) {
        StringBuilder sb = new StringBuilder();
        String sample = "The quick brown fox jumps over the lazy dog. ";
        while (sb.length() < sizeInBytes) {
            sb.append(sample);
        }
        return sb.substring(0, sizeInBytes).getBytes();
    }

    public static void main(String[] args) throws Exception {
        runExperiments();
    }

    private static void runExperiments() throws Exception {
        System.out.println("=== DES Symmetric Encryption Experiments ===\n");

        SecretKey key = generateKey();
        System.out.println("Generated DES Key (hex): " + bytesToHex(key.getEncoded()));
        System.out.println("Key length: " + (key.getEncoded().length * 8)
                + " bits (56 effective, 8 parity)\n");

        int[] sizes = {1024, 10240, 102400, 512000, 1048576};
        String[] labels = {"1 KB", "10 KB", "100 KB", "500 KB", "1 MB"};

        System.out.println("--- Encryption/Decryption Timing ---");
        System.out.printf("%-10s %-15s %-15s %-20s %-8s%n",
                "Size", "Encrypt (ms)", "Decrypt (ms)", "Throughput (bps)", "Match");
        System.out.println("-".repeat(68));

What does this code do?

generateTestData fills a byte array with repeated text up to the exact size you request. This gives you predictable, repeatable test data at any file size.

The old main now delegates to runExperiments(), keeping the entry point clean. The method generates a fresh DES key, defines five sizes from 1 KB to 1 MB, and prints a formatted table header.

The method is not finished yet. You still need the loop that encrypts and decrypts at each size.

  • Directly below the System.out.println("-".repeat(68)); line you just pasted, add the timing loop that closes the method:
        byte[] lastOriginal = null;
        byte[] lastEncrypted = null;

        for (int i = 0; i < sizes.length; i++) {
            byte[] data = generateTestData(sizes[i]);

            long startEnc = System.nanoTime();
            byte[] encrypted = encrypt(data, key);
            long encTime = System.nanoTime() - startEnc;

            long startDec = System.nanoTime();
            byte[] decrypted = decrypt(encrypted, key);
            long decTime = System.nanoTime() - startDec;

            boolean match = Arrays.equals(data, decrypted);

            double encMs = encTime / 1_000_000.0;
            double decMs = decTime / 1_000_000.0;
            double throughputBps = (sizes[i] * 8.0) / (encTime / 1_000_000_000.0);

            System.out.printf("%-10s %-15.3f %-15.3f %-20.0f %-8s%n",
                    labels[i], encMs, decMs, throughputBps, match);

            if (i == sizes.length - 1) {
                lastOriginal = data;
                lastEncrypted = encrypted;
            }
        }
    }

What does this loop do?

  • For each of the five sizes, it generates test data, encrypts it, and records the time using System.nanoTime() before and after.
  • It decrypts the ciphertext and checks that the result matches the original using Arrays.equals.
  • Throughput is calculated as (sizeInBytes * 8.0) / (encryptionTimeNanos / 1_000_000_000.0), which gives bits per second.
  • The last iteration stores the 1 MB original and encrypted data in lastOriginal and lastEncrypted so later experiments can use them.
  • Save DESEncryptor.java.

Before you run this, how do you think encryption time will change as the file size grows? Will it double when the file doubles, or something different?

  • Compile and run the program by running this command in your terminal:
javac DESEncryptor.java && java DESEncryptor

You should see a formatted table with five rows. Each row shows the file size, encryption time in milliseconds, decryption time, throughput in bits per second, and a true in the Match column confirming the decrypted data is identical to the original.

Seeing a compilation error?

  • Check that you deleted the entire old main method body. Leftover lines from the string encryption test will cause duplicate variable errors.
  • Make sure the closing } of runExperiments is present after the loop's closing brace. You need one } for the loop and one } for the method.
  • Verify that generateTestData is placed inside the DESEncryptor class but outside runExperiments. It is a separate method, not nested inside another.

Still stuck? Ask for help.

Test decryption with the wrong key

In symmetric encryption, the secret key is the only thing protecting your data. If an attacker gets the key, the encryption is broken. If they use a different key, the decryption should fail completely.

  • In DESEncryptor.java, find the closing } of the runExperiments method.
  • Add this code directly above that closing brace to save the 1 MB files to disk and run the wrong-key test:
        Files.write(Path.of("original_1mb.bin"), lastOriginal);
        Files.write(Path.of("encrypted_1mb.bin"), lastEncrypted);
        System.out.println("\n1 MB files saved to disk.\n");

        System.out.println("--- Wrong Key Experiment ---");
        SecretKey wrongKey = generateKey();
        System.out.println("Correct key: " + bytesToHex(key.getEncoded()));
        System.out.println("Wrong key:   " + bytesToHex(wrongKey.getEncoded()));
        try {
            decrypt(lastEncrypted, wrongKey);
            System.out.println("Result: Decryption produced output (data is garbage).\n");
        } catch (Exception e) {
            System.out.println("Result: " + e.getClass().getSimpleName()
                    + " - " + e.getMessage());
            System.out.println("Decryption with wrong key FAILED as expected.\n");
        }

What does this code do?

  • Files.write saves the 1 MB original and encrypted data as original_1mb.bin and encrypted_1mb.bin. You will use these files in the next step for compression and transfer experiments.
  • A second DES key (wrongKey) is generated and used to attempt decryption of the 1 MB ciphertext.
  • The try/catch block handles the expected failure. With PKCS5 padding, the wrong key almost always produces invalid padding bytes, which throws an exception.
  • Save DESEncryptor.java.

Before you run this, what do you think happens when you try to decrypt ciphertext with the wrong key? Will the program crash, produce garbage, or something else?

  • Compile and run the full program by running this command:
javac DESEncryptor.java && java DESEncryptor

Below the timing table, you should see both keys printed in hex, followed by a BadPaddingException message confirming that decryption with the wrong key failed. The wrong key scrambles the padding bytes so badly that Java detects the corruption and refuses to return any output.

Why does the wrong key cause BadPaddingException?

DES with PKCS5 padding appends special bytes to the end of your data before encryption. When you decrypt with the correct key, those padding bytes follow a predictable pattern. When you use the wrong key, the decrypted padding bytes are random garbage, and Java throws BadPaddingException because the pattern is invalid.

In rare cases, the random bytes might accidentally form valid-looking padding. The code handles this too, printing that the output is garbage instead of throwing an exception.

🙋‍♀️ Not seeing the wrong-key output?

  • Make sure the new code is inside runExperiments, after the timing loop's closing } but before the method's closing }.
  • Check that you have two files in your des-lab folder: original_1mb.bin and encrypted_1mb.bin. If they are missing, the file-writing code may not be executing.

Still stuck? Ask for help.

  • Confirm the saved files exist by running:
ls *.bin

You should see encrypted_1mb.bin and original_1mb.bin in the output.

✔️ Awesome, I've got everything!

Great. Double check that you have saved DESEncryptor.java and that both .bin files appear in your des-lab folder.

ⓧ I'd like to double check the full code

Your DESEncryptor.java is now too long to show in one block. Scroll up and confirm each section is present in this order:

  • All import statements at the top (unchanged from the previous step).
  • The static initializer block that re-enables DES.
  • generateKey(), encrypt(), and decrypt() methods (unchanged).
  • The new generateTestData(int sizeInBytes) method.
  • bytesToHex() helper (unchanged).
  • The new main calling runExperiments().
  • The runExperiments() method containing: key generation, five-size timing loop, file saves (original_1mb.bin and encrypted_1mb.bin), and the wrong-key experiment with try/catch.

Your program now produces timing data across five file sizes and proves that the wrong key cannot unlock your ciphertext. Next up, you will compare compression ratios between original and encrypted files, and simulate transferring your key to a separate program instance.

Add Compression and Transfer Simulation

In the previous step, you proved your encryption works across five file sizes and showed that the wrong key breaks decryption. Your assignment also asks two deeper questions: does encrypted data compress differently from the original, and can you transfer ciphertext and a key to another program instance?

In this step, you will ZIP the original and encrypted 1 MB files to compare compression ratios. Then you will save the DES key to a file, load it back, and decrypt the saved ciphertext to simulate a real transfer.

In this step, get ready to:
  • Add methods to save, load, and ZIP-compress data.
  • Compare compression ratios between original and encrypted files.
  • Simulate transferring ciphertext and a key between program instances.
Add key persistence methods and save the key to disk

To transfer a key between separate program runs, you need a way to write it to a file and read it back. Raw key bytes are binary, so you will encode them with Base64 to make them safe for text-based storage.

  • In DESEncryptor.java, find the decrypt method's closing brace.
  • Add these two new methods directly below it, before the generateTestData method:
    // Save a secret key to a file as Base64 text
    public static void saveKey(SecretKey key, String filename) throws Exception {
        Files.write(Path.of(filename), Base64.getEncoder().encode(key.getEncoded()));
    }

    // Load a secret key from a Base64-encoded file
    public static SecretKey loadKey(String filename) throws Exception {
        byte[] encoded = Files.readAllBytes(Path.of(filename));
        byte[] keyBytes = Base64.getDecoder().decode(encoded);
        return new SecretKeySpec(keyBytes, "DES");
    }

What does this code do?

saveKey encodes the raw key bytes as a Base64 string and writes them to a file. This makes the key safe to store as plain text.

loadKey reads that file back, decodes the Base64, and reconstructs a SecretKey object using SecretKeySpec. This is how you transfer a key between separate program runs.

Now you need to actually call saveKey inside runExperiments so the key is written to disk every time you run the program.

  • Scroll down to the runExperiments method and find this line:
        System.out.println("Key length: " + (key.getEncoded().length * 8)
                + " bits (56 effective, 8 parity)\n");
  • Add these two lines directly below it, before the int[] sizes declaration:
        // Persist the key to disk for later transfer simulation
        saveKey(key, "des_key.txt");
        System.out.println("Key saved to des_key.txt\n");
  • Save DESEncryptor.java.
  • Compile and run the program by running this command in your terminal:
javac DESEncryptor.java && java DESEncryptor

After the timing table and wrong-key experiment, you should now see a new line: Key saved to des_key.txt.

  • Confirm the key file was created by running:
ls

You should see des_key.txt alongside DESEncryptor.java, original_1mb.bin, and encrypted_1mb.bin.

Not seeing des_key.txt?

  • Make sure the saveKey(key, "des_key.txt"); line is placed after the key generation, not inside the for loop.
  • Check that your terminal is in the des-lab folder. Files are written to the current working directory.
  • Why isn't my saveKey method creating des_key.txt?
Add compression comparison for original and encrypted files

Your assignment asks you to ZIP the original and encrypted files and compare their compressed sizes. This test reveals a core property of encryption: good ciphertext has high entropy (it looks random), so compression algorithms cannot shrink it.

  • In DESEncryptor.java, find the bytesToHex method. Add this new method directly above it, after the closing brace of generateTestData:
    // ZIP-compress data and return the resulting file size in bytes
    public static long zipAndGetSize(byte[] data, String zipFilename) throws Exception {
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFilename))) {
            zos.putNextEntry(new ZipEntry("data"));
            zos.write(data);
            zos.closeEntry();
        }
        return new File(zipFilename).length();
    }

What does this code do?

zipAndGetSize writes the given byte array into a ZIP archive, then returns the file size of that archive. By comparing the ZIP size against the original data size, you can calculate how much the data compressed.

  • Scroll down to the runExperiments method and find the closing brace of the wrong-key catch block (the last line of the wrong-key experiment section).
  • Add the compression comparison code directly below it:
        // ZIP both the original and encrypted 1 MB files, then compare
        System.out.println("--- Compression Comparison (1 MB file) ---");
        long originalZipSize = zipAndGetSize(lastOriginal, "original_zipped.zip");
        long encryptedZipSize = zipAndGetSize(lastEncrypted, "encrypted_zipped.zip");
        System.out.println("Original file size:     " + lastOriginal.length + " bytes");
        System.out.println("Original zipped size:   " + originalZipSize + " bytes");
        System.out.println("Encrypted file size:    " + lastEncrypted.length + " bytes");
        System.out.println("Encrypted zipped size:  " + encryptedZipSize + " bytes");
        System.out.printf("Original compression:   %.1f%%%n",
                (1 - (double) originalZipSize / lastOriginal.length) * 100);
        System.out.printf("Encrypted compression:  %.1f%%%n",
                (1 - (double) encryptedZipSize / lastEncrypted.length) * 100);

What does this code do?

This calls zipAndGetSize on both the original 1 MB data and the encrypted 1 MB data. It then prints the raw and compressed sizes side by side.

The compression percentage formula (1 - zipSize / originalSize) * 100 tells you how much smaller the ZIP file is compared to the original. A high percentage means lots of redundancy was squeezed out. A near-zero percentage means the data was already too random to compress.

  • Save DESEncryptor.java.
  • Compile and run the program again:
javac DESEncryptor.java && java DESEncryptor

After the wrong-key experiment, you should now see a Compression Comparison section. The original file compresses dramatically (a high percentage), while the encrypted file barely compresses at all (near 0%).

Why does encrypted data resist compression?

Compression works by finding repeated patterns in data. Your original file is the same sentence repeated thousands of times, so ZIP can replace all those copies with a single reference.

Good encryption scrambles data so thoroughly that it looks random. There are no patterns left for ZIP to exploit. This is why the encrypted file's ZIP is almost the same size as the original encrypted file.

🙋‍♀️ Seeing a compilation error?

  • Check that the zipAndGetSize method is placed inside the DESEncryptor class (between the closing brace of generateTestData and the opening of bytesToHex), not inside another method.
  • Verify that the compression code is placed after the wrong-key experiment's closing brace, not inside the try/catch block.
  • Why am I getting a compilation error after adding the compression comparison?
Simulate key transfer between program instances

Your assignment asks you to transfer ciphertext and a key to "another instance" of the application. This simulation loads the key from the file you just saved, reads the encrypted file from disk, and decrypts it. It proves that a second program run can recover the original data if it has the right key file.

  • In runExperiments, find the last System.out.printf line that prints the encrypted compression percentage.
  • Add the transfer simulation code directly below it:
        // Simulate transferring ciphertext and key to another program instance
        System.out.println("\n--- Transfer Simulation ---");
        SecretKey loadedKey = loadKey("des_key.txt");
        byte[] transferredCiphertext = Files.readAllBytes(Path.of("encrypted_1mb.bin"));
        byte[] recovered = decrypt(transferredCiphertext, loadedKey);
        boolean transferMatch = Arrays.equals(lastOriginal, recovered);
        System.out.println("Key loaded from file:   " + bytesToHex(loadedKey.getEncoded()));
        System.out.println("Transfer decryption:    "
                + (transferMatch ? "SUCCESS" : "FAILED"));

        System.out.println("\n=== Experiments Complete ===");

What does this code do?

loadKey("des_key.txt") reads the saved key from disk and reconstructs a SecretKey object. Then Files.readAllBytes reads the encrypted file from disk, just as a second program instance would.

The decrypted output is compared against the original data using Arrays.equals. If they match, the transfer succeeded. This proves the key and ciphertext survived being saved to disk and loaded back.

  • Save DESEncryptor.java.

Before you run the program, think about what you expect to see. Will the transfer simulation print SUCCESS or FAILED? Why?

  • Compile and run the full program:
javac DESEncryptor.java && java DESEncryptor

At the very bottom of the output, you should see the Transfer Simulation section. It prints the loaded key in hex (matching the generated key above) and Transfer decryption: SUCCESS. The program ends with === Experiments Complete ===.

Why does this prove key transfer works?

The simulation reads the key and ciphertext entirely from files on disk, not from variables in memory. A completely separate program run could do the exact same thing: read des_key.txt and encrypted_1mb.bin, call loadKey and decrypt, and recover the original data.

This also highlights the key distribution problem: if someone intercepts des_key.txt during transfer, they can decrypt everything. Symmetric encryption only works when both parties can share the key securely.

🙋‍♀️ Seeing FAILED instead of SUCCESS?

  • Make sure the saveKey(key, "des_key.txt") call appears near the top of runExperiments, right after the key is generated. If it is missing, the transfer simulation tries to load a stale key from a previous run.
  • Check that loadKey reads the same filename (des_key.txt) that saveKey writes to. A filename mismatch loads the wrong key.
  • My transfer simulation prints FAILED. How do I debug this?

✔️ Awesome, I've got everything!

Great. Double check you have saved DESEncryptor.java.

ⓧ I'd like to double check the full code

The full file is too long to show in one block. Scroll up through your DESEncryptor.java and confirm these sections are present, in this order:

  • All 13 import statements at the top (including Base64, ZipEntry, and ZipOutputStream).
  • The static initializer block that checks for disabled algorithms.
  • generateKey, encrypt, and decrypt methods.
  • saveKey and loadKey methods (new in this step).
  • generateTestData method.
  • zipAndGetSize method (new in this step).
  • bytesToHex method.
  • main calling runExperiments().
  • runExperiments containing: key generation, saveKey call, timing table loop, file saves, wrong-key test, compression comparison, and transfer simulation ending with === Experiments Complete ===.

Your experiment suite is complete. You have timing data, a wrong-key failure, compression proof, and a transfer simulation all in one program. Next up, you will take on the Secret Mission and add a command-line interface so the program works as a real encrypt/decrypt tool across independent runs.

Secret mission

Add a Command-Line Interface

Your assignment requires transferring ciphertext to another instance of the application. Right now, the transfer simulation happens inside a single program run. Can you add a command-line interface with keygen, encrypt, and decrypt modes, so each operation runs as a truly independent execution?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • des-lab project folder (includes DESEncryptor.java, generated .bin, .zip, and .txt files).
  • JDK 21 (installed on your system).
  • Extension Pack for Java (VS Code extension).

Keep everything running

No action needed. Choose this if you plan to continue working on this project or want the code available for your assignment write-up.

  • Your des-lab folder stays on your Desktop with all generated files.
  • JDK 21 and the Extension Pack for Java remain installed for future Java projects.

Pause - I'll come back to this later

Close VS Code to free up memory. Your project files stay on disk and are ready when you return.

  • Close VS Code.
  • Your des-lab folder and all generated files remain on your Desktop.
  • To resume, open the des-lab folder in VS Code again.

Delete - I don't want to use this again

Deleting the project folder permanently removes your code and generated files, so copy anything you want to keep first. You can also uninstall the Java tools when you no longer need them.

  • Close VS Code if it is still open.
  • Delete the project folder by running this command in your terminal.
rm -rf ~/Desktop/des-lab
  • Confirm the project folder is gone by listing the contents of your Desktop:
ls ~/Desktop

You should no longer see des-lab in the output. Your project files have been removed.

  • Remove the Java extension pack and OpenJDK 21 from Omarchy by running these commands:
code --uninstall-extension vscjava.vscode-java-pack
omarchy pkg drop jdk21-openjdk

VS Code confirms that the extension was removed. Omarchy lists the JDK package removal before returning you to the prompt.

Nice Work!

Nice Work!

You just built a complete DES encryption laboratory in Java from scratch. You generated secret keys, encrypted and decrypted files across five sizes, broke decryption with the wrong key, and proved that encrypted data resists compression.

What you learned:

  • Generated DES secret keys using KeyGenerator, encrypted and decrypted files across five sizes (1 KB to 1 MB), and recorded timing and throughput data in bits per second.
  • Observed decryption fail with the wrong key, proving that the secret key is the sole basis of security in symmetric encryption.
  • Compared ZIP compression ratios between original and encrypted files, and simulated key transfer between program instances by saving and loading keys from files.
  • Secret Mission: Added a command-line interface with keygen, encrypt, and decrypt modes, enabling true cross-instance transfer via separate program executions.

Ready to quiz yourself?