0

I am reading a large file in small-small bytes chunks. I am encrypting that file, chunk by chunk, using AES 128 bit encryption and writing each encrypted chunk into another file. The File is Encrypted successfully.

But when I am reading that encrypted file back in small-small bytes chunks and trying to decrypt that file- chunk by chunk, it throws an Exception

java - javax.crypto.BadPaddingException: Given final block not properly padded 

But when I am trying to read the whole file and try to decrypt bytes, it decryptes successfully.

Here is my code:

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * Created by Ashish Pancholi on 22-12-2016.
 */
public class EncryDecryPtion implements Securable{
    public static void main(String[] argu){
        EncryDecryPtion encryDecryPtion = new EncryDecryPtion();
        File file = new File("shouldbeoriginal.jpg");
        try {
            encryDecryPtion.encryptFile(file,"Pa$$w0rd");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        }

        File file_ = new File("shouldbeoriginal.jpg");
        try {
            encryDecryPtion.decryptFile(file_,"Pa$$w0rd");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        }
    }
} 




import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * Encrypt and decrypt file with AES algorithm
 * Created by Ashish Pancholi on 20-12-2016.
 */
public interface Securable {

    /**
     * Read and write the file in chunk.
     * Encrypts the chunks with AES algorithm.
     * It creates a new a file which having encrypted data,
     * deletes old original file and
     * rename a new file with the old file
     * @param file which is to be encrypted and password.
     */

    default File encryptFile(File file, String password) throws IOException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException {
        AESEncryptionDecryption aesEncryptionDecryption = new AESEncryptionDecryption(password);
        String encryptedFilePath = file.getAbsolutePath() + ".ENCRYPTED";
        File encryptedFile = new File(encryptedFilePath);
        encryptedFile.createNewFile();
        try
                (FileInputStream in = new FileInputStream(file)) {

            try
                    (OutputStream out = new FileOutputStream(encryptedFile)) {
                byte[] chunk = new byte[1024];
                int chunkLen = 0;
                while ((chunkLen = in.read(chunk)) != -1) {
                    byte[] encryptedChunk = aesEncryptionDecryption.encrypt(chunk);
                    out.write(encryptedChunk);
                }
            }
        }
        Path path_originalFile = Paths.get(file.getAbsolutePath());
        Path path_encryptedFile = Paths.get(encryptedFile.getAbsolutePath());
        try {
            Files.delete(path_originalFile);
        }catch (IOException ex){
            try {
                FileUtils.forceDelete(file);
            }catch (IOException ex1){
                //ignore
            }
        }
        Path path = Files.move(path_encryptedFile, path_originalFile);
        return path.toFile();
    }

    default File encryptWholeFile(File file, String password) throws IOException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException {
        AESEncryptionDecryption aesEncryptionDecryption = new AESEncryptionDecryption(password);
        String encryptedFilePath = file.getAbsolutePath() + ".ENCRYPTED";
        File encryptedFile = new File(encryptedFilePath);
        encryptedFile.createNewFile();
        try(FileInputStream in = new FileInputStream(file)) {
            byte[] bytes = IOUtils.toByteArray(in);
            byte[] encryptedChunk = aesEncryptionDecryption.encrypt(bytes);
            FileUtils.writeByteArrayToFile(encryptedFile, encryptedChunk);
        }
        Path path_originalFile = Paths.get(file.getAbsolutePath());
        Path path_encryptedFile = Paths.get(encryptedFile.getAbsolutePath());
        try {
            Files.delete(path_originalFile);
        }catch (IOException ex){
            try {
                FileUtils.forceDelete(file);
            }catch (IOException ex1){
                //ignore
            }
        }
        Path path = Files.move(path_encryptedFile, path_originalFile);
        return path.toFile();
    }

    /**
     * Read and write the file in chunk.
     * Encrypts the chunks with AES algorithm.
     * It creates a new a file which having encrypted data,
     * deletes old original file and
     * rename a new file with the old file
     * @param inputStream of file which is to be encrypted and a password.
     */
    default InputStream encryptFile(InputStream inputStream, String password) throws IOException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException {
        InputStream in;
        try {
            AESEncryptionDecryption aesEncryptionDecryption = new AESEncryptionDecryption(password);
            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                byte[] chunk = new byte[1024];
                int chunkLen = 0;
                while ((chunkLen = inputStream.read(chunk)) != -1) {
                    byte[] encryptedChunk = aesEncryptionDecryption.encrypt(chunk);
                    baos.write(encryptedChunk);
                }
                baos.flush();
                in = new ByteArrayInputStream(baos.toByteArray());
            }
        }finally {
            inputStream.close();
        }
        return in;
    }

    /**
     * Read and write the file in chunk.
     * Encrypts the chunks with AES algorithm.
     * It creates a new a file which having encrypted data,
     * deletes old original file and
     * rename a new file with the old file
     * @param inputStream of file which is to be encrypted and a password.
     */
    default File encryptFile(InputStream inputStream, String password, String targetFileName) throws IOException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException {
        File encryptedFile = new File(targetFileName);
        try {
            AESEncryptionDecryption aesEncryptionDecryption = new AESEncryptionDecryption(password);
            encryptedFile.getParentFile().mkdirs();
            encryptedFile.createNewFile();
            try (OutputStream baos = new FileOutputStream(encryptedFile)) {
                byte[] chunk = new byte[1024];
                int chunkLen = 0;
                while ((chunkLen = inputStream.read(chunk)) != -1) {
                    byte[] encryptedChunk = aesEncryptionDecryption.encrypt(chunk);
                    baos.write(encryptedChunk);
                }
            }
        }finally {
            inputStream.close();
        }
        return encryptedFile;

    }

    /**
     * Read and write the file in chunk.
     * Decrypts the chunks with AES algorithm.
     * It creates a new a file which having decrypted data,
     * deletes old original encrypted file and
     * rename a new file with the old file
     * @param file which is to be decrypted and password.
     */

    default void decryptFile(File file, String password) throws IOException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException {
        AESEncryptionDecryption aesEncryptionDecryption = new AESEncryptionDecryption(password);
        String decryptedFilePath = file.getAbsolutePath() + ".DECRYPTED";
        File decryptedFile = new File(decryptedFilePath);
        decryptedFile.createNewFile();
        try
                (FileInputStream in = new FileInputStream(file)) {

            try
                    (OutputStream out = new FileOutputStream(decryptedFile)) {
                byte[] chunk = new byte[1024];
                int chunkLen = 0;
                while ((chunkLen = in.read(chunk)) != -1) {
                    byte[] encryptedChunk = aesEncryptionDecryption.decrypt(chunk);
                    out.write(encryptedChunk);
                }
            }
        }
        Path path_originalFile = Paths.get(file.getAbsolutePath());
        Path path_decryptedFile = Paths.get(decryptedFile.getAbsolutePath());
        try {
            Files.delete(path_originalFile);
        }catch (IOException ex){
            try {
                FileUtils.forceDelete(file);
            }catch (IOException ex1){
                //ignore
            }
        }
        Files.move(path_decryptedFile, path_originalFile);
    }

    default File decryptWholeFile(File file, String password) throws IOException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException {
        AESEncryptionDecryption aesEncryptionDecryption = new AESEncryptionDecryption(password);
        String decryptedFilePath = file.getAbsolutePath() + ".DECRYPTED";
        File decryptedFile = new File(decryptedFilePath);
        decryptedFile.createNewFile();
        try(FileInputStream in = new FileInputStream(file)) {
                byte[] bytes = IOUtils.toByteArray(in);
                byte[] encryptedChunk = aesEncryptionDecryption.decrypt(bytes);
                FileUtils.writeByteArrayToFile(decryptedFile, encryptedChunk);
          }
        Path path_originalFile = Paths.get(file.getAbsolutePath());
        Path path_decryptedFile = Paths.get(decryptedFile.getAbsolutePath());
        try {
            Files.delete(path_originalFile);
        }catch (IOException ex){
            try {
                FileUtils.forceDelete(file);
            }catch (IOException ex1){
                //ignore
            }
        }
       Path path =  Files.move(path_decryptedFile, path_originalFile);
        return path.toFile();
    }

}


import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;


/**
 * Encrypt and decrypt file with AES algorithm
 * Created by Ashish Pancholi on 20-12-2016.
 */

public class AESEncryptionDecryption {

    private SecretKeySpec secretKey;
    private byte[] key;

    public AESEncryptionDecryption(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException{
        MessageDigest sha = null;
        key = password.getBytes("UTF-8");
        sha = MessageDigest.getInstance("SHA-1");
        key = sha.digest(key);
        key = Arrays.copyOf(key, 16); // use only first 128 bit
        this.secretKey = new SecretKeySpec(key, "AES");
    }

    /**
     * Encrypts the file with AES algorithm
     * @param bytes of file which is to encrypted
     * @return byte[] which is encrypted bytes
     */
    public byte[] encrypt(byte[] bytes) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, this.secretKey);
            byte[] encrytedBytes = cipher.doFinal(bytes);
            return encrytedBytes;

    }

    /**
     * Decrypts the file with AES algorithm
     * @param encrytedBytes of file that to be decrypted
     * @return byte[] which is original data.
     */
    public byte[] decrypt(byte[] encrytedBytes) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException
    {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, this.secretKey);
        byte[] bytes = cipher.doFinal(encrytedBytes);
        return bytes;
    }
}

SO my Question is- How to encrypt a large file without loading the whole file in the memory? Please help.

EDITED

Here is the updated code but still I am getting the same exception on line cipher.doFinal() when decrypting:

public void encrypt(File sourceFile, File targetFile) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, IOException {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, this.secretKey);
    try(InputStream inputStream = new FileInputStream(sourceFile)){
        try(OutputStream outputStream = new FileOutputStream(targetFile)){
            byte[] chunk = new byte[8192];
            int chunkLen = 0;
            while ((chunkLen = inputStream.read(chunk)) != -1) {
                byte[] encrytedBytes = cipher.update(chunk);
                outputStream.write(encrytedBytes);
            }
            byte[] finalBytes =  cipher.doFinal();
            if(finalBytes!=null) {
                outputStream.write(finalBytes);
            }
        }
    }
}

public void decrypt(File encryptedFile, File targetFile) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, IOException {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, this.secretKey);
    try(InputStream inputStream = new FileInputStream(encryptedFile)){
        try(OutputStream outputStream = new FileOutputStream(targetFile)){
            byte[] chunk = new byte[8192];
            int chunkLen = 0;
            while ((chunkLen = inputStream.read(chunk)) != -1) {
                byte[] decrytedBytes = cipher.update(chunk);
                outputStream.write(decrytedBytes);
            }
            byte[] finalBytes =  cipher.doFinal();
            if(finalBytes!=null) {
                outputStream.write(finalBytes);
            }
        }
    }
}
Ashish Pancholi
  • 4,249
  • 9
  • 43
  • 80

2 Answers2

4

You should use the cipher.update(...) methods when you stream through the file, and only use the cipher.doFinal(...) as the last call. The doFinal flushes the buffers + do padding etc, that you don't want to do more than one time.

If you do a doFinal(...) in your decrypt prematurely, it will (very likely) fail as a correct padding is missing in the data.

Edit:

Don't use ECB mode it's insecure. Take a look here and scroll down to the penguin.

Don't generate your keys by a simple Sha-1 - use a proper key deriviation function like PBKDF2WithHmacSHA256.

Community
  • 1
  • 1
Ebbe M. Pedersen
  • 6,530
  • 3
  • 23
  • 41
  • I tried your suggestion. Thank you very much. But still, I am not able to decrypt the file. Actually while decrypting the file, I followed the same way that you suggested in your answer, I used `cipher.update(...)` when I stream through a file but when I used `cipher.doFinal(...)` as the last call, it throwing an Exception again- `javax.crypto.BadPaddingException: Given final block not properly padded`. – Ashish Pancholi Dec 26 '16 at 06:54
  • One strange thing is also happening here - In `decryption`, when I commented out the `cipher.doFinal(...)` from code then file decrypted successfully (only, by using `cipher.update(...)`) . So do we not need to use `doFinal(...)` while `decrypting`? I wonder if that really encrypted. Is that very easy to decrypt the file by hackers? – Ashish Pancholi Dec 26 '16 at 07:07
  • 1
    Basically `cipher.update(...)` would not fail on decrypt, even with random input as it don't inspect the data it decrypt. If you omit the `doFinal(...)` you will end up missing the last block of data as this will be stuck in a buffer to allow for proper padding. How big is your encrypted data compared to the data in clear? It should be 1 to 16 bytes bigger than the input (padded to nearest multiple of 16). – Ebbe M. Pedersen Dec 26 '16 at 08:45
  • No change in size. BTW.. I have updated the question by adding two methods at the end in which I am doing exactly what you have suggested. Please look into these method and let me know if I am doing something wrong. Unfortunately I am still getting the same Exception when calling `doFinal(...)` at the time of `decryption`. Please help. Thanks in advance. – Ashish Pancholi Dec 26 '16 at 09:20
  • You encrypt the full buffer with `cipher.update(chunk)`, even when chunkLen is less than 8192 .. you need to tell how much of the data in the buffer you want encrypted when data is less than 8192 .. – Ebbe M. Pedersen Dec 26 '16 at 10:14
  • Like `byte[] encrytedBytes = cipher.update(chunk, 0, chunkLen);` .. same goes for decrypt .. – Ebbe M. Pedersen Dec 26 '16 at 11:43
0

Initially I was using cipher.doFinal() for both Encryption and Decryption modes. Runtime Exceution was fine but testcases were failing with BadPaddingException(). Then I changed the code to

byte[] output = this.cipher.update(input, 0, input.length);

And now the decryption is working fine. Same goes for Encryption as well.

Suraj Rao
  • 28,186
  • 10
  • 88
  • 94