【问题标题】:java - javax.crypto.BadPaddingException: Given final block not properly paddedjava - javax.crypto.BadPaddingException:给定的最终块未正确填充
【发布时间】:2016-12-22 15:07:43
【问题描述】:

我是reading 一个large filebytes chunks。我是encrypting 那个文件,逐块使用AES 128 bit encryptionwriting 将每个加密块放入另一个文件中。该文件是Encryptedsuccessfully

但是当我reading 那个encrypted 文件回到小文件bytes chunks 并试图逐块地decrypt 那个文件时,它会抛出一个异常

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

但是当我试图读取整个文件并尝试decrypt 字节时,它decryptes 成功。

这是我的代码:

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;
    }
}

所以我的问题是 - 如何加密大文件而不将整个文件加载到内存中?请帮忙。

已编辑

这是更新后的代码,但我在解密时仍然在cipher.doFinal() 线上遇到同样的异常:

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);
            }
        }
    }
}

【问题讨论】:

  • 为什么在单词中间随机大写P?
  • 我同意这是错误的类命名方式。感谢您指出。

标签: java file encryption memory-management aes


【解决方案1】:

当您流式传输文件时,您应该使用cipher.update(...) 方法,并且只使用cipher.doFinal(...) 作为最后一次调用。 doFinal 刷新缓冲区+做填充等,你不想做超过一次。

如果您过早地在解密中执行doFinal(...),它(很可能)会失败,因为数据中缺少正确的填充。

编辑:

不要使用 ECB 模式,它不安全。看看here 并向下滚动到企鹅。

不要通过简单的 Sha-1 生成密钥 - 使用正确的密钥派生函数,例如 PBKDF2WithHmacSHA256

【讨论】:

  • 我尝试了你的建议。非常感谢你。但是,我仍然无法解密文件。实际上,在解密文件时,我遵循了您在回答中建议的相同方式,当我通过文件流式传输时使用cipher.update(...),但是当我使用cipher.doFinal(...) 作为最后一次调用时,它再次抛出异常-javax.crypto.BadPaddingException: Given final block not properly padded .
  • 这里也发生了一件奇怪的事情——在decryption 中,当我从代码中注释掉cipher.doFinal(...) 时,文件解密成功(仅通过使用cipher.update(...))。那么我们不需要使用doFinal(...)decrypting 吗?我想知道那是否真的加密了。黑客解密文件这么容易吗?
  • 基本上cipher.update(...) 不会在解密时失败,即使是随机输入,因为它不检查它解密的数据。如果您省略doFinal(...),您最终将丢失最后一个数据块,因为这将卡在缓冲区中以允许适当的填充。与明文数据相比,您的加密数据有多大?它应该比输入大 1 到 16 个字节(填充到最接近的 16 倍数)。
  • 大小没有变化。顺便说一句..我已经通过在最后添加两种方法来更新问题,我正在按照您的建议进行操作。请查看这些方法,如果我做错了什么,请告诉我。不幸的是,在decryption 时调用doFinal(...) 时,我仍然遇到同样的异常。请帮忙。提前致谢。
  • 您使用cipher.update(chunk) 加密整个缓冲区,即使chunkLen 小于8192 .. 当数据小于8192 时,您需要告诉缓冲区中有多少数据要加密..
【解决方案2】:

最初我将cipher.doFinal() 用于加密和解密模式。运行时执行很好,但测试用例因BadPaddingException() 而失败。然后我把代码改成

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

现在解密工作正常。加密也是如此。

【讨论】:

  • 这对我有用。非常感谢!!
猜你喜欢
  • 1970-01-01
  • 2011-05-16
  • 2012-05-12
  • 2018-03-03
  • 1970-01-01
  • 2017-03-20
  • 2016-06-05
  • 2018-08-17
  • 1970-01-01
相关资源
最近更新 更多