使用带有 128 位密钥的 AES 和 PBKDF2 的 Java 密码体系结构进行文件加密

File Encryption with the Java Cryptography Architecture using AES with a 128-bit key and PBKDF2

我在解密文件时遇到此错误

我正在使用 PBKDF2 将密码短语转换为密钥,然后使用它。加密工作正常,但是当我尝试解密同一个文件时,出现以下错误。除了最后几行(可能是填充区域)外,解密文件给出了正确的数据。我通过在加密和解密时输出IV和密钥来调试它,它们都是相同的,但错误仍然存​​在。

public class FileEncryptorSkeleton{

    private static final String progName = "FileEncryptor";
    private static final int bufSize = 128;

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

        BufferedInputStream in = null;          // A buffered input stream to read from
        BufferedOutputStream out = null;        // And a buffered output stream to write to
        SecretKeyFactory kf = null;             // Something to create a key for us
        KeySpec ks = null;                      // This is how we specify what kind of key we want it to generate
        byte[] salt = new byte[20];             // Some salt for use with PBKDF2, only not very salty
        SecretKey key = null;                   // The key that it generates
        Cipher cipher = null;                   // The cipher that will do the real work
        SecretKeySpec keyspec = null;           // How we pass the key to the Cipher
        int bytesRead = 0;                      // Number of bytes read into the input file buffer
        byte[] iv = new byte[16];
        // First, check the user has provided all the required arguments, and if they haven't, tell them then exit
        if(args.length != 4) {
            printUsageMessage(); System.exit(1);
        }

        // Open the input file
        try {
            in = new BufferedInputStream(new FileInputStream(args[1]));
        } catch (FileNotFoundException e) {
            printErrorMessage("Unable to open input file: " + args[1], null);
            System.exit(1);
        }

        // And then the output file
        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 ex) {
                Logger.getLogger(FileEncryptorSkeleton.class.getName()).log(Level.SEVERE, null, ex);
            }

        // Set up a KeySpec for password-based key generation of a 128-bit key
            ks = new PBEKeySpec(args[3].toCharArray(), salt, 1000, 128);




        // Now run the passphrase through PBKDF2 to get the key
            try {
                    key = kf.generateSecret(ks);
                    }catch(InvalidKeySpecException e){
        System.exit(1);
                    }

        // Get the byte encoded key value as a byte array
        byte[] aeskey = key.getEncoded();

        // Now generate a Cipher object for AES encryption in ECBC mode with PKCS #5 padding
        // Use ECB for the first task, then switch to CBC for versions 2 and 3
        try {
            cipher = Cipher.getInstance("AES/CBC/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);
        }

        // Set a variable to indicate whether we're in encrypt or decrypt mode, based upon args[0]
        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);
        }

        // Set up a secret key specification, based on the 16-byte (128-bit) AES key array previously generated
        keyspec = new SecretKeySpec(aeskey, "AES");
        IvParameterSpec ivspec = new IvParameterSpec(iv);
        // Now initialize the cipher in the right mode, with the keyspec and the ivspec
        try {

            cipher.init(cipherMode, keyspec,ivspec);

        } catch (InvalidKeyException e) {
            printErrorMessage("Invalid Key Spec",e); System.exit(2);
        } catch (InvalidAlgorithmParameterException ex) {
                Logger.getLogger(FileEncryptorSkeleton.class.getName()).log(Level.SEVERE, null, ex);
            }

        // Set up some input and output byte array buffers
        byte[] inputBuffer = new byte[bufSize];
        byte[] outputBuffer = null;

        // "Prime the pump" - we've got to read something before we can encrypt it
        // and not encrypt anything if we read nothing.
        try {
            bytesRead = in.read(inputBuffer);
        } catch (IOException e) {
            printErrorMessage("Error reading input file " + args[1],e); System.exit(1);
        }

        // As long as we've read something, loop around encrypting, writing and reading
        // bytesRead will be zero if nothing was read, or -1 on EOF - treat them both the same
        while (bytesRead > 0) {

            // Now encrypt this block
            outputBuffer = cipher.update(inputBuffer.toString().getBytes("UTF-8"));         
            // Write the generated block to file
            try {
                out.write(outputBuffer);
            } catch (IOException e) {
                printErrorMessage("Error writing to output file " + args[2],e); System.exit(1);
            }

            // And read in the next block of the file
            try {
                bytesRead = in.read(inputBuffer);
            } catch (IOException e) {
                printErrorMessage("Error reading input file " + args[1],e); System.exit(1);
            }
        }

            try {
                // Now do the final processing
                outputBuffer =cipher.doFinal(inputBuffer.toString().getBytes("UTF-8"));
                cipher.init(cipherMode, keyspec,ivspec);
                System.out.println(ivspec+"   "+cipherMode+"   "+keyspec);
            } catch (IllegalBlockSizeException ex) {
                Logger.getLogger(FileEncryptorSkeleton.class.getName()).log(Level.SEVERE, null, ex);
            } catch (BadPaddingException ex) {
                Logger.getLogger(FileEncryptorSkeleton.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InvalidKeyException ex) {
                Logger.getLogger(FileEncryptorSkeleton.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InvalidAlgorithmParameterException ex) {
                Logger.getLogger(FileEncryptorSkeleton.class.getName()).log(Level.SEVERE, null, ex);
            }

        // Write the final block of output
        try {
            out.write(outputBuffer);
        } catch (IOException e) {
            printErrorMessage("Error on final write to output file " + args[2],e); System.exit(1);
        }

        // Close the output files
        try {
            in.close();
            out.close();
        } catch (IOException e) {
            printErrorMessage("Error closing file", e);
        }

        // If we were continuing beyond this point, we should really overwrite key material, drop KeySpecs, etc.
    }

    /**
        * Print an error message on , optionally picking up additional detail from
        * a passed exception
        * @param errMsg
        * @param e
        */
    private static void printErrorMessage(String errMsg, Exception e) {
        System.err.println(errMsg);
        if (e != null) 
            System.err.println(e.getMessage());
    }

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

}

Oct 18, 2019 11:27:46 PM FileEncryptorSkeleton main
SEVERE: null
javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
        at com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
        at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056)
        at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
        at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
        at javax.crypto.Cipher.doFinal(Cipher.java:2164)
        at FileEncryptorSkeleton.main(FileEncryptorSkeleton.java:174)

Cipher#update-和Cipher#doFinal-方法都使用inputBuffer.toString(),它只包含对象的名称class和哈希码,而不是实际数据缓冲区。

inputBuffer-byte[] 中读取前 bytesRead 个字节(之前从 in-BufferedInputStream 中读取)并处理它们 (Cipher#update) 是正确的:

outputBuffer = cipher.update(inputBuffer, 0, bytesRead); 

包含 cipher#update 调用的循环仅在没有字节被读取到 inputBuffer-byte[] 时才会保留,因此最终处理适用 (Cipher#doFinal):

outputBuffer = cipher.doFinal();

此外,在 cipher#doFinal 调用之后的第二个 cipher#init 调用是不必要的 (Cipher#init)。