0
votes

I have coded up a Java program which asks the user to specify a random secret key (in base 64 encoding), to specify the plaintext file to encrypt, and the name of the output file (the encrypted text file.)

Then the reverse to decrypt (secret key + input encrypted file + name of output decrypted file).

This follows the very similar behavior to what OpenSSL offers.

My java code uses the mode AES/CBC/PKCS5PADDING.

I have appended my randomly generated initialization vector (16 byte IV), to my ciphertext beforehand using the file output stream. Then I believe the cipher output stream writes the ciphertext after the IV.

I can confirm that the generated and fetched IV from both the encrypt and decrypt method is the same (both can be printed out with base64 encoding, and will match).

The problem, however, lies with trying to decrypt the encrypted text. The decrypted text is able to show the second half of the encrypted text (which matches the second half of the original plain text).

Example:

Plain text: my secret motto: i am awesome!

Cipher text: <lots of encrypted characters>

Decrypted text: <a few encrypted characters> i am awesome!

The first half though seems to be overwritten or perhaps collected some weird leftover encrypted text. This leads me to believe that something is not quite right with how I am encrypting/decrypting the IV using streams.

The relevant executed code is when the boolean fileStoreIV is true. The else code is for when the user provides both the key and IV as inputs.

Therefore fout.write(initVector); and encryptedData.read(fileIV); are the main bits of code.

Encryption method:

private static void encrypt(byte[] key, byte[] initVector, String inputFile, String outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IOException {
    //Initalisation for encryption
    Cipher cipher = Cipher.getInstance(CIPHER);
    if(fileStoreIV) {
        SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
    }
    else {
        IvParameterSpec iv = new IvParameterSpec(initVector);
        SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
    }
    
    //File error checking
    File loadFile = new File(inputFile);
    Path saveFile = Paths.get(outputFile);
    
    Path loadFilePath = Paths.get(inputFile);
    if (!Files.exists(loadFilePath)){
        System.out.println("The inputFile you specified does not exist");
        return;
    }
    
    Path parentDir = saveFile.getParent();
    if (parentDir != null && !Files.exists(parentDir)) {
        System.out.println("The outputFile directory/s you specified does not exist");
        return;
    }
    
    System.out.println("Secret key is " + Base64.getEncoder().encodeToString(key));
    System.out.println("IV is " + Base64.getEncoder().encodeToString(initVector));

    //Special file reading and writing with 'Cipher Stream'
    try (InputStream fin = FileEncryptor.class.getResourceAsStream(loadFile.getName());
            OutputStream fout = Files.newOutputStream(saveFile);
            
            CipherOutputStream cipherOut = new CipherOutputStream(fout, cipher) {
    }) {
        final byte[] bytes = new byte[1024];
        for(int length=fin.read(bytes); length!=-1; length = fin.read(bytes)){
            
            
            if(fileStoreIV) {
                fout.write(initVector);
                fileStoreIV = false;
            }
            
            cipherOut.write(bytes, 0, length);
            
        }
    } catch (IOException e) {
        System.out.println("Something went wrong with reading and writing these files!");
        System.out.println("Please check you have the latest version of this program");
        System.out.println("Contact your IT admin to make sure you have sufficient privileges");
    }
    System.out.println("SUCCESS! Encryption finished, saved at specified location");

Decryption method:

private static void decrypt(String inputKEY, String inputIV, String inputFile, String outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, IOException, InvalidKeyException, InvalidAlgorithmParameterException {
    //Initalisation for decryption
    Cipher cipher = Cipher.getInstance(CIPHER);
    if(!fileStoreIV) {
        IvParameterSpec iv = new IvParameterSpec(Base64.getDecoder().decode(inputIV));
        SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
    }
    
    Path saveFile = Paths.get(outputFile);
    
    Path loadFilePath = Paths.get(inputFile);
    if (!Files.exists(loadFilePath)){
        System.out.println("The inputFile you specified does not exist");
        return;
    }
    
    Path parentDir = saveFile.getParent();
    if (parentDir != null && !Files.exists(parentDir)) {
        System.out.println("The outputFile directory/s you specified does not exist");
        return;
    }
    
    InputStream encryptedData = Files.newInputStream(loadFilePath);
    
    if(fileStoreIV) {
        {
            byte[] fileIV = new byte[16];
            encryptedData.read(fileIV);
            System.out.println(Base64.getEncoder().encodeToString(fileIV));
                SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(fileIV));
                fileStoreIV = false;
        }
    }
    
        try (CipherInputStream decryptStream = new CipherInputStream(encryptedData, cipher);    
        OutputStream decryptedOut = Files.newOutputStream(saveFile)){
        final byte[] bytes = new byte[1024];
        for(int length=decryptStream.read(bytes); length!=-1; length = decryptStream.read(bytes)){
            decryptedOut.write(bytes, 0, length);
        }
    } catch (IOException e) {
        System.out.println("Something went wrong with reading and writing these files!");
        System.out.println("Please check you have the latest version of this program");
        System.out.println("Contact your IT admin to make sure you have sufficient privileges");
    }
    
    System.out.println("SUCESS! Decryption finished, saved at specified location");

Additional note: when I add enough whitespaces before and inside the plain text file. I manage to shift enough of the text over, to get the decrypted file to display: <a few encrypted characters> my secret motto: i am awesome!.

1

1 Answers

4
votes

The main part why decryption is not running successfully is this part of your code in Encrypt-method:

if(fileStoreIV) {
     SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
     cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
} else {
     IvParameterSpec iv = new IvParameterSpec(initVector);
     SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
     cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
}

When fileStoreIV == true you don't specify the IV for the cipher.init function. And because the IV was wrong, the first block (16 bytes for AES) of decrypted plaintext was replaced by effectively random garbage; this might be half, all, or a tiny fraction of the plaintext depending on its length (as per comment of @dave_thompson_085).

I modified your code as it does not run out of the box due to some other errors but I'm too lazy to correct them, instead you find a full running example code below.

Here is the output of the program, my plaintext-file contains the text The quick brown fox jumps over the lazy dog:

Secret key is S2guVMqVk8goYy3QsgBSMmjLLCyvoknprTGoFsxMZEo=
IV is VFIYWeCT6ixg/lwk9bBQ9g==
SUCCESS! Encryption finished, saved at specified location
SUCESS! Decryption finished, saved at specified location
Content of file decryptedtext.txt
The quick brown fox jumps over the lazy dog

code:

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class FileEncryptorSo {
    static String ALGORITHM = "AES";
    static String CIPHER = "AES/CBC/PKCS5PADDING";
    static boolean fileStoreIV = true;

    public static void main(String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
        System.out.println("");
        String plaintextFilename = "plaintext.txt";
        String ciphertextFilename = "ciphertext.enc";
        String decryptedFilename = "decryptedtext.txt";
        // random aes 256 key
        byte[] key = new byte[32];
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.nextBytes(key);
        // random iv
        byte[] iv = new byte[16];
        secureRandom.nextBytes(iv);
        encrypt(key, iv, plaintextFilename, ciphertextFilename);
        decrypt(Base64.getEncoder().encodeToString(key), Base64.getEncoder().encodeToString(iv), ciphertextFilename, decryptedFilename);
        printTextfile(decryptedFilename);
    }

    private static void encrypt(byte[] key, byte[] initVector, String inputFile, String outputFile)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
        //Initalisation for encryption
        Cipher cipher = Cipher.getInstance(CIPHER);
        IvParameterSpec iv = new IvParameterSpec(initVector);
        SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        /* ### leave out this part !
        if (fileStoreIV) {
            SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        } else {
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        }
         */
        //File error checking
        // ### not used File loadFile = new File(inputFile);
        Path saveFile = Paths.get(outputFile);
        Path loadFilePath = Paths.get(inputFile);
        if (!Files.exists(loadFilePath)) {
            System.out.println("The inputFile you specified does not exist");
            return;
        }
        Path parentDir = saveFile.getParent();
        if (parentDir != null && !Files.exists(parentDir)) {
            System.out.println("The outputFile directory/s you specified does not exist");
            return;
        }
        System.out.println("Secret key is " + Base64.getEncoder().encodeToString(key));
        System.out.println("IV is " + Base64.getEncoder().encodeToString(initVector));
        try (FileInputStream in = new FileInputStream(inputFile);
             FileOutputStream out = new FileOutputStream(outputFile);
             CipherOutputStream encryptedOutputStream = new CipherOutputStream(out, cipher);) {
            if (fileStoreIV) {
                out.write(initVector);
                // ### leave out this line fileStoreIV = false;
            }
            byte[] buffer = new byte[1024];
            int nread;
            while ((nread = in.read(buffer)) > 0) {
                encryptedOutputStream.write(buffer, 0, nread);
            }
            encryptedOutputStream.flush();
        } catch (IOException e) {
            System.out.println("Something went wrong with reading and writing these files!");
            System.out.println("Please check you have the latest version of this program");
            System.out.println("Contact your IT admin to make sure you have sufficient privileges");
        }
        System.out.println("SUCCESS! Encryption finished, saved at specified location");
    }

    private static void decrypt(String inputKEY, String inputIV, String inputFile, String outputFile)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
        //Initalisation for decryption
        Cipher cipher = Cipher.getInstance(CIPHER);
        if (!fileStoreIV) {
            IvParameterSpec iv = new IvParameterSpec(Base64.getDecoder().decode(inputIV));
            SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
        }
        Path saveFile = Paths.get(outputFile);
        Path loadFilePath = Paths.get(inputFile);
        if (!Files.exists(loadFilePath)) {
            System.out.println("The inputFile you specified does not exist");
            return;
        }
        Path parentDir = saveFile.getParent();
        if (parentDir != null && !Files.exists(parentDir)) {
            System.out.println("The outputFile directory/s you specified does not exist");
            return;
        }
        //byte[] fileIV = new byte[16];
        try (FileInputStream in = new FileInputStream(inputFile);
             CipherInputStream cipherInputStream = new CipherInputStream(in, cipher);
             FileOutputStream out = new FileOutputStream(outputFile))
        {
            byte[] buffer = new byte[1024];
            if (fileStoreIV) {
                byte[] fileIV = new byte[16];
                in.read(fileIV);
                SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(fileIV));
                // ### leave out his line fileStoreIV = false;
            }
            int nread;
            while ((nread = cipherInputStream.read(buffer)) > 0) {
                out.write(buffer, 0, nread);
            }
            out.flush();
        } catch (IOException e) {
            System.out.println("Something went wrong with reading and writing these files!");
            System.out.println("Please check you have the latest version of this program");
            System.out.println("Contact your IT admin to make sure you have sufficient privileges");
        }
        System.out.println("SUCESS! Decryption finished, saved at specified location");
    }

    private static void printTextfile (String filename) throws IOException {
        File file = new File(filename);
        FileInputStream fis = new FileInputStream(file);
        byte[] data = new byte[(int) file.length()];
        fis.read(data);
        fis.close();
        String str = new String(data, "UTF-8");
        System.out.println("Content of file " + filename + "\n" + str);
    }
}