encryption - 使用带有 128 位密钥和 PBKDF2 的 AES 使用 Java 加密体系结构进行文件加密
问题描述
解密文件时出现此错误
我正在使用 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()
,它只包含对象类的名称和哈希码,而不是缓冲区中的实际数据。
从 中读取第一个bytesRead
字节inputBuffer-byte[]
(之前从 中读取in-BufferedInputStream
)并处理它们(Cipher#update
)是正确的:
outputBuffer = cipher.update(inputBuffer, 0, bytesRead);
包含cipher#update
-call 的循环仅在没有字节被读取到 时才留下inputBuffer-byte[]
,因此适用于最终处理 ( Cipher#doFinal
):
outputBuffer = cipher.doFinal();
此外,不需要cipher#init
紧跟在 -call 之后的第二个 -call cipher#doFinal
( Cipher#init
)。