使用CipherInputStream / CipherOutputStream的最后一个块不完整,即使使用填充AES / CBC / PKCS5Padding

其实,我也是从网上search的,也是为了这个,

最初我没有在我的encryption和解密中使用填充,

但最后我从这里得到了解决办法

https://stackoverflow.com/a/10775577/1115788

我更新了我的代码与填充作为AES / CBC / PKCS5Padding和相同的错误即将到来,最后一个块不解密…

我正在为此工作最后两天,但没有find解决办法

我的Crypter代码:

package mani.droid.browsedropbox; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Crypter { Cipher encipher; Cipher decipher; CipherInputStream cis; CipherOutputStream cos; FileInputStream fis; byte[] ivbytes = new byte[]{(byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o', (byte)'p'}; IvParameterSpec iv = new IvParameterSpec(ivbytes); public boolean enCrypt(String key, InputStream is, OutputStream os) { try { byte[] encoded = new BigInteger(key, 16).toByteArray(); SecretKey seckey = new SecretKeySpec(encoded, "AES"); encipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); encipher.init(Cipher.ENCRYPT_MODE, seckey, iv); cis = new CipherInputStream(is, encipher); copyByte(cis, os); return true; } catch (InvalidKeyException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchPaddingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { // TODO Auto-generated catch block e.printStackTrace(); } return false; } public boolean deCrypt(String key, InputStream is, OutputStream os) { try { byte[] encoded = new BigInteger(key, 16).toByteArray(); SecretKey seckey = new SecretKeySpec(encoded, "AES"); encipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); encipher.init(Cipher.DECRYPT_MODE, seckey, iv); cos = new CipherOutputStream(os, encipher); copyByte(is, cos); //cos.close(); return true; } catch (InvalidKeyException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchPaddingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { // TODO Auto-generated catch block e.printStackTrace(); } return false; } public void copyByte(InputStream is, OutputStream os) throws IOException { byte[] buf = new byte[8192]; int numbytes; while((numbytes = is.read(buf)) != -1) { os.write(buf, 0, numbytes); os.flush(); } os.close(); is.close(); } } 

Solutions Collecting From Web of "使用CipherInputStream / CipherOutputStream的最后一个块不完整,即使使用填充AES / CBC / PKCS5Padding"

我有完全一样的问题。 被接受的解决scheme是可行的,因为你使用了不需要填充的密码模式,但这不是encryption相关的问题。

根据CipherOutputStream 文档 ,您必须调用close()方法才能正确完成encryption(即添加填充块)。

此方法调用封装密码对象的doFinal方法,这会导致由封装密码缓冲的任何字节被处理。 结果是通过调用这个输出stream的flush方法写出来的。

此方法将封装的密码对象重置为初始状态,并调用基础输出stream的close方法。

如果要在调用CipherOutputStream.close()方法后保留OutputStream打开状态,则可以将OutputStream封装到不closures它的stream中。 例如:

 public class NotClosingOutputStream extends OutputStream { private final OutputStream os; public NotClosingOutputStream(OutputStream os) { this.os = os; } @Override public void write(int b) throws IOException { os.write(b); } @Override public void close() throws IOException { // not closing the stream. } @Override public void flush() throws IOException { os.flush(); } @Override public void write(byte[] buffer, int offset, int count) throws IOException { os.write(buffer, offset, count); } @Override public void write(byte[] buffer) throws IOException { os.write(buffer); } } 

那么你可以使用:

 ... cos = new CipherOutputStream(new NotClosingOutputStream(os), encipher); copyByte(is, cos); cos.close(); ... 

请注意, osstream不会closures,您需要在适当的时候自行完成。

最后我得到了我自己的问题的答案,试验和错误其实这里冲突是我设置encipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

并设置了一些值…..,

最后我得到的答案只是取代了algorithm

从:

AES / CBC / PKCS7Paddinng

至:

AES / CFB8 / NoPadding

它的工作就像魅力….所以我build议这个答案为谁在这个问题上挣扎,如果你解决了你的问题,在这里提到他人…

我见过CipherInputStream失败,填充问题。 这种行为在不同版本的JVM之间是不同的。 例如7u55 32位我的代码工作正常,7u55 64位相同的代码失败…我也看到了后来的32位JVM失败。 解决方法是使用字节数组方法并避免CipherInputStream。

不知道这是否与OP的问题有关,但这可能有助于某人。

当你重复地得到这个java.io.IOException: last block incomplete in decryption 无论你改变了什么,检查你是否仍在使用之前运行的文件。 如果您的读/写testing代码附加到该文件,您将永远得到该例外 – 除非您删除您写入的损坏的文件。

使用CipherInputStream与填充是可能的,切换到NoPadding是一种解决方法,但不是一个解决scheme。

当CipherInputStream到达stream结尾时应用填充。 重要的一点是你必须调用CipherInputStream的read()方法至less两次来获取所有的数据。

以下示例演示如何使用padding读取CipherInputStream:

 public static void test() throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecureRandom rnd = new SecureRandom(); byte[] keyData = new byte[16]; byte[] iv = new byte[16]; rnd.nextBytes(keyData); rnd.nextBytes(iv); SecretKeySpec key = new SecretKeySpec(keyData, "AES"); cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); CipherOutputStream out = new CipherOutputStream(buffer, cipher); byte[] plain = "Test1234567890_ABCDEFG".getBytes(); out.write(plain); out.flush(); out.close(); byte[] encrypted = buffer.toByteArray(); System.out.println("Plaintext length: " + plain.length); System.out.println("Padding length : " + (cipher.getBlockSize() - (plain.length % cipher.getBlockSize()))); System.out.println("Cipher length : " + encrypted.length); cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); CipherInputStream in = new CipherInputStream(new ByteArrayInputStream(encrypted), cipher); buffer = new ByteArrayOutputStream(); byte[] b = new byte[100]; int read; while ((read = in.read(b)) >= 0) { buffer.write(b, 0, read); } in.close(); // prints Test1234567890_ABCDEFG System.out.println(new String(buffer.toByteArray())); }