我想加密,然后解密文件使用AES。我读过很多关于错误"Given final block not properly padded"
的话题。但我找不到解决办法。
对不起,我的代码指定语言,我不知道写语言java
下面是我的代码: <<p> 变量/strong>// IV, secret, salt in the same time
private byte[] salt = { 'h', 'u', 'n', 'g', 'd', 'h', '9', '4' };
public byte[] iv;
public SecretKey secret;
createSecretKey
public void createSecretKey(String password){
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
secret = new SecretKeySpec(tmp.getEncoded(), "AES");
}
方法加密
public void encrypt(String inputFile){
FileInputStream fis = new FileInputStream(inputFile);
// Save file: inputFile.enc
FileOutputStream fos = new FileOutputStream(inputFile + ".enc");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
// Gen Initialization Vector
iv = (byte[]) ((IvParameterSpec) params
.getParameterSpec(IvParameterSpec.class)).getIV();
// read from file (plaint text) -----> save with .enc
int readByte;
byte[] buffer = new byte[1024];
while ((readByte = fis.read(buffer)) != -1) {
fos.write(cipher.doFinal(buffer), 0, readByte);
}
fis.close();
fos.flush();
fos.close();
}
方法解密
public void decrypt(String inputFile){
FileInputStream fis = new FileInputStream(inputFile);
// Save file: filename.dec
FileOutputStream fos = new FileOutputStream(inputFile.substring(0,
inputFile.length() - 4) + ".dec");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
// Read from file encrypted ---> .dec
int readByte;
byte[] buffer = new byte[1024];
while ((readByte = fis.read(buffer)) != -1) {
fos.write(cipher.doFinal(buffer), 0, readByte);
}
fos.flush();
fos.close();
fis.close();
}
解决方案:buffer
的编辑大小为16的倍数。使用CipherInput/Output读取/写入文件
Artjom B.
AES是分组密码,因此只能在16字节的块上工作。像CBC这样的操作模式使您能够将多个块链接在一起。诸如pkcs# 5填充之类的填充使您能够通过填充明文直到块大小的下一个倍数来加密任意长度的明文。
问题是你对每1024字节分别加密。因为1024除以块大小,所以填充在加密之前添加一个完整的块。因此,密文块有1040字节长。然后在解密期间,您只读取1024,缺少填充。Java尝试解密它,然后尝试删除填充。如果填充格式不正确(因为它不存在),则抛出异常。
容易解决只需将您的解密缓冲区增加到1040字节。
正确修复
不要在单独的块中加密,而是使用Cipher#update(byte[], int, int)
而不是Cipher.doFinal
来更新您读取的每个缓冲区的密文,或者使用CipherInputStream
。
其他安全注意事项:
您丢失了一个随机IV。如果没有它,攻击者可能仅通过观察密文就可以看到您在相同的密钥下加密了相同的明文。
您缺少密文认证。如果没有它,您就无法可靠地检测到密文中的(恶意)更改,并且可能使您的系统受到诸如填充oracle攻击之类的攻击。要么使用像GCM这样的身份验证模式,要么通过HMAC运行创建的密文来创建身份验证标记并将其写到末尾。然后,您可以在解密期间/之前验证标记。
您错误地认为加密数据的长度等于普通数据的长度,但加密的AES数据始终是AES块大小(16字节)的倍数,并且可以有一个额外的完整填充块。
处理流加密的最有效方法是使用JCE的CipherOutputStream和CipherInputStream (http://docs.oracle.com/javase/7/docs/api/javax/crypto/CipherInputStream.html)。这些类为你做了所有的工作。另外,请确保始终将新生成的IV保存在加密方法中,以便能够使用它进行解密。