javaencryptionaesecbjavax.crypto

BadPaddingException when reading CipherInputStream during decryption


I'm new to Java cryptography and don't know why I keep getting BadPaddingException when attempting to decrypt a file that I have successfully encrypted, using the same key and IV for both. I am adding the 16-byte IV to the beginning of the ciphertext file and then attempting to read it during decryption. This and the initialization of the secret key appears to work so I'm unsure where I've gone wrong.

I am using javac -g FileEncryptor.java Util.java to compile and java FileEncryptor enc 90F6seEyxAK9cxblqsKJGQ== input.txt ciphertext.enc and java FileEncryptor dec 90F6seEyxAK9cxblqsKJGQ== ciphertext.enc decrypted.txt for encryption and decryption respectively.

Help is appreciated :)

I've checked that the IV and secret key are 16 bytes, and whether the IV is being prepended (during encryption) and read (during decryption) properly. During debugging, I will always get the exception as soon as I reach the for loop containing decryptStream.read(bytes).

Note: not all of this code is mine. I'm using this as a learning exercise.

public class FileEncryptor {
    private static final Logger LOG = Logger.getLogger(FileEncryptor.class.getSimpleName());

    private static final String ALGORITHM = "AES";
    private static final String CIPHER = "AES/CBC/PKCS5PADDING";

    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
            InvalidAlgorithmParameterException, IOException, Exception {

        SecureRandom sr = new SecureRandom();
        byte[] key = new byte[16];
        sr.nextBytes(key); // 128 bit key
        byte[] initVector = new byte[16];
        sr.nextBytes(initVector);
        String mode = args[0];
        String hexSkey;

        IvParameterSpec iv = new IvParameterSpec(initVector);
        SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(CIPHER);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

        final Path currentPath = Paths.get("");

        if (mode.equals("enc")) {

            System.out.println("initVector=" + Util.bytesToHex(initVector));

            skeySpec = new SecretKeySpec(Base64.getDecoder().decode(args[1]), ALGORITHM);

            final Path encryptedPath = currentPath.resolve(args[3]);// idk if this is necessary imo,

            // args[1] becomes base64 encoded skey
            // args[2] = input.txt
            // args[3] = output encrypted file
            try (InputStream fin = FileEncryptor.class.getResourceAsStream(args[2]);

                    OutputStream fout = Files.newOutputStream(encryptedPath);
                    CipherOutputStream cipherOut = new CipherOutputStream(fout, cipher) {
                    }) {
                final byte[] bytes = new byte[1024];
                fout.write(initVector);// write the IV to the first 16 bytes of the encrypted file
                for (int length = fin.read(bytes); length != -1; length = fin.read(bytes)) {
                    cipherOut.write(bytes, 0, length);
                }
                cipherOut.close();
            } catch (IOException e) {
                LOG.log(Level.INFO, "Unable to encrypt", e);
            }
            return;
        } else if (mode.equals("dec")) {
            String encodedFile = args[2], decodedFile = args[3];
            skeySpec = new SecretKeySpec(Base64.getDecoder().decode(args[1]), ALGORITHM);
            final Path decryptedPath = currentPath.resolve(decodedFile);

            try (InputStream encryptedData = Files.newInputStream(Path.of(encodedFile));
                    OutputStream decryptedOut = Files.newOutputStream(decryptedPath)) {
                byte[] storedIV = new byte[16];
                int first16bytes = encryptedData.read(storedIV);
                if (first16bytes != storedIV.length) {
                    // FIXME: edit error here
                }
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(storedIV));

                System.out.println("key check " + storedIV.length);
                final byte[] bytes = new byte[1024];

                CipherInputStream decryptStream = new CipherInputStream(encryptedData, cipher);
                for (int length = decryptStream.read(bytes); length != -1; length =
                decryptStream.read(bytes)) {
                System.out.println("reached");
                decryptedOut.write(bytes, 0, length);
                }
                decryptStream.close();
            } catch (IOException ex) {
                Logger.getLogger(FileEncryptor.class.getName()).log(Level.SEVERE, "Unable to decrypt", ex);
            }
            LOG.info("Decryption complete, open " + decryptedPath);
            return;// delete later
        }
    }
}

Solution

  • You're generating a random key and using it in the cipher instead of using the supplied key. That will (usually) generate a bad padding exception, as the ciphertext would decrypt to random plaintext, and there is only about a 1/256 chance of getting correct padding (and garbage plaintext).

    ChatGPT solved the issue automatically when I asked it to clean up the code:

    import java.io.*;
    import java.nio.file.*;
    import java.security.*;
    import java.util.Base64;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.crypto.*;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    
    public class FileEncryptor {
        private static final Logger LOG = Logger.getLogger(FileEncryptor.class.getSimpleName());
        private static final String ALGORITHM = "AES";
        private static final String CIPHER = "AES/CBC/PKCS5PADDING";
    
        public static void main(String[] args) {
            try {
                String mode = args[0];
                byte[] key = Base64.getDecoder().decode(args[1]);
                SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
                Cipher cipher = Cipher.getInstance(CIPHER);
                
                if (mode.equals("enc")) {
                    encrypt(skeySpec, cipher, args[2], args[3]);
                } else if (mode.equals("dec")) {
                    decrypt(skeySpec, cipher, args[2], args[3]);
                }
            } catch (Exception e) {
                LOG.log(Level.SEVERE, "Operation failed", e);
            }
        }
    
        private static void encrypt(SecretKeySpec skeySpec, Cipher cipher, String inputFile, String outputFile) throws Exception {
            SecureRandom sr = new SecureRandom();
            byte[] initVector = new byte[16];
            sr.nextBytes(initVector);
            IvParameterSpec iv = new IvParameterSpec(initVector);
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
    
            try (InputStream fin = FileEncryptor.class.getResourceAsStream(inputFile);
                 OutputStream fout = Files.newOutputStream(Paths.get(outputFile));
                 CipherOutputStream cipherOut = new CipherOutputStream(fout, cipher)) {
                fout.write(initVector);
                byte[] bytes = new byte[1024];
                for (int length = fin.read(bytes); length != -1; length = fin.read(bytes)) {
                    cipherOut.write(bytes, 0, length);
                }
            } catch (IOException e) {
                LOG.log(Level.INFO, "Unable to encrypt", e);
            }
        }
    
        private static void decrypt(SecretKeySpec skeySpec, Cipher cipher, String inputFile, String outputFile) throws Exception {
            try (InputStream encryptedData = Files.newInputStream(Path.of(inputFile));
                 OutputStream decryptedOut = Files.newOutputStream(Paths.get(outputFile))) {
                byte[] storedIV = new byte[16];
                int first16bytes = encryptedData.read(storedIV);
                if (first16bytes != storedIV.length) {
                    throw new IOException("Invalid IV length");
                }
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(storedIV));
    
                CipherInputStream decryptStream = new CipherInputStream(encryptedData, cipher);
                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) {
                LOG.log(Level.SEVERE, "Unable to decrypt", e);
            }
        }
    }