Search code examples
javaencryptioncryptographyaes

Bad Padding Exception on decrypt but still works


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;


public class Encryptor{

    private static final int bufferSize= 128;

    /**
     * @param args
     */
    public static void main(String[] args) {

        BufferedInputStream in = null; 
        BufferedOutputStream out = null; 
        SecretKeyFactory kf = null; 
        KeySpec ks = null; 
        byte[] salt = new byte[20]; 
        SecretKey key = null; 
        Cipher cipher = null; 
        SecretKeySpec keyspec = null; 
        int bytesRead = 0; 
        if (args.length != 4) {
            printUsageMessage();
            System.exit(1);
        }
        try {
            in = new BufferedInputStream(new FileInputStream(args[1]));
        } catch (FileNotFoundException e) {
            printErrorMessage("Unable to open input file: " + args[1], null);
            System.exit(1);
        }
        try {
            out = new BufferedOutputStream(new FileOutputStream(args[2]));
        } catch (FileNotFoundException e) {
            printErrorMessage("Unable to open output file: " + args[2], e);
            System.exit(1);
        }
        try {
            kf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        } catch (NoSuchAlgorithmException e2) {
            e2.printStackTrace();
        }
        String password = args[3];

        ks = new PBEKeySpec(password.toCharArray(), salt, 128, 128);
        try {
            key = kf.generateSecret(ks);
        } catch (InvalidKeySpecException e1) {
            e1.printStackTrace();
        }
        byte[] aeskey = key.getEncoded();
        try {

            cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        } catch (NoSuchAlgorithmException e) {
            printErrorMessage("No Such Algorithm Exception when creating main cipher", e);
            System.exit(2);
        } catch (NoSuchPaddingException e) {
            printErrorMessage("No Such Padding Exception when creating main cipher", e);
            System.exit(2);
        }
        int cipherMode = -1;
        char mode = Character.toLowerCase(args[0].charAt(0));
        switch (mode) {
        case 'e':
            cipherMode = Cipher.ENCRYPT_MODE;
            break;
        case 'd':
            cipherMode = Cipher.DECRYPT_MODE;
            break;
        default:
            printUsageMessage();
            System.exit(1);
        }
        keyspec = new SecretKeySpec(aeskey, "AES");
        try {
            cipher.init(cipherMode, keyspec);
        } catch (InvalidKeyException e) {
            printErrorMessage("Invalid Key Spec", e);
            System.exit(2);
        }
        byte[] inputBuffer = new byte[bufferSize];
        byte[] outputBuffer = null;
        try {
            bytesRead = in.read(inputBuffer);
        } catch (IOException e) {
            printErrorMessage("Error reading input file " + args[1], e);
            System.exit(1);
        }
        while (bytesRead > 0) {
            outputBuffer = cipher.update(inputBuffer);
            try {
                out.write(outputBuffer);
            } catch (IOException e) {
                printErrorMessage("Error writing to output file " + args[2], e);
                System.exit(1);
            }
            try {
                bytesRead = in.read(inputBuffer);
            } catch (IOException e) {
                printErrorMessage("Error reading input file " + args[1], e);
                System.exit(1);
            }
        }
        try {
            outputBuffer = cipher.doFinal(inputBuffer);
        } catch (IllegalBlockSizeException | BadPaddingException e1) {
            e1.printStackTrace();
        }
        try {
            out.write(outputBuffer);
        } catch (IOException e) {
            printErrorMessage("Error on final write to output file " + args[2], e);
            System.exit(1);
        }
        try {
            in.close();
            out.close();
        } catch (IOException e) {
            printErrorMessage("Error closing file", e);
        }
    }


    private static void printErrorMessage(String errMsg, Exception e) {
        System.err.println(errMsg);
        if (e != null)
            System.err.println(e.getMessage());
    }


    private static void printUsageMessage() {
        System.out.println(progName + " $Revision: 1.1 $: Usage: " + progName + " E/D infile outfile passphrase");
    }

}

I am trying to write a program that encrypts and decrypts a text document using a password. It doesnt throw any errors on encrypt, but it does throw a bad padding exception on decrypt, but it still outputs the correct text but with extra stuff on the end. I have searched other answers but cannot find a solution. The program is run by compiling into runnable jar and running it like java -jar filename e/d (encrypt/decrypt) inputFile.txt outputFile.txt password.

Thanks in Advance

EDIT:

Exception:
javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
        at java.base/com.sun.crypto.provider.CipherCore.unpad(Unknown Source)
        at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(Unknown Source)
        at java.base/com.sun.crypto.provider.CipherCore.doFinal(Unknown Source)
        at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(Unknown Source)
        at java.base/javax.crypto.Cipher.doFinal(Unknown Source)
        at FileEncryptorSkeleton.main(FileEncryptorSkeleton.java:183)

this is where I add outputBuffer = cipher.doFinal(inputBuffer);

Sample Input:

aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz

Sample Output:

aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazz
aaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzzaaaazzzz

output picture as invalid characters didnt show up in cod block


Solution

  • You repeatedly call bytesRead = in.read(inputBuffer) then cipher.update(inputBuffer). When the end of the input file is reached, only part of inputBuffer is set to new data and the rest is leftover residue from the previous read, but you use all of it. Then you call cipher.doFinal(inputBuffer) which uses another copy of whatever is left in the buffer after the last read.

    On encryption, this results in encrypting repetitions of some data from the last few lines (up to 128 bytes). On decryption, this results in calling doFinal with data that is not actually the last part of the ciphertext, thus the 'bad padding' exception.

    Instead do (modulo error handling for clarity):

    bytesRead = in.read(inputBuffer);
    while( bytesRead > 0 ){
        outputBuffer = cipher.update(inputBuffer, 0, readBytes); // only use the part read 
        out.write(outputBuffer);
        bytesRead = in.read(inputBuffer);
    }
    outputBuffer = cipher.doFinal(); // no data at all here, .update already processed it
    out.write(outputBuffer);
    

    PS: ECB applied to general data (like a 'text document') is almost always insecure; google ECB penguin. And PBKDF2 with only 128 iterations is not very good, and with constant salt is very bad. But those are security issues and offtopic here.