Java 使用CipherInputStream/CipherOutputStream的最后一个块不完整,即使使用填充AES/CBC/PKCS5P也是如此
事实上,我在网上和stackoverflow上搜索了很多 最初我在加密和解密中没有使用填充 但最后我从这里得到了解决办法 我用padding将代码更新为AES/CBC/pkcs5p 同样的错误也来了,最后一个块没有被解密 过去两天我一直在做这个,但没有找到解决方案 我的密码:Java 使用CipherInputStream/CipherOutputStream的最后一个块不完整,即使使用填充AES/CBC/PKCS5P也是如此,java,android,encryption,aes,padding,Java,Android,Encryption,Aes,Padding,事实上,我在网上和stackoverflow上搜索了很多 最初我在加密和解密中没有使用填充 但最后我从这里得到了解决办法 我用padding将代码更新为AES/CBC/pkcs5p 同样的错误也来了,最后一个块没有被解密 过去两天我一直在做这个,但没有找到解决方案 我的密码: package mani.droid.browsedropbox; import java.io.FileInputStream; import java.io.IOException; import java.io.
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();
}
}
经过反复试验,我终于找到了自己问题的答案 实际上这里的冲突是我在
encipher=Cipher.getInstance(“AES/CBC/PKCS7Padding”)中设置了填充代码>
用一些值设置IV
最后我得到的答案只是替换了算法
发件人:
AES/CBC/PKCS7Paddinng
致:
AES/CFB8/NOP8
它的作用就像魅力一样……,所以我建议其他人用这个答案来解决这个问题,如果你解决了你的问题,请在这里为其他人提一下……我也遇到了同样的问题。
公认的解决方案之所以有效,是因为您使用了不需要填充的密码模式,但这不是解决密码相关问题的方法
根据CipherOutputStream,必须调用close()方法才能正确完成加密(即添加填充块)
此方法调用封装密码的doFinal方法
对象,这将导致封装的密码缓冲的所有字节
被处理。通过调用的flush方法写出结果
这个输出流
此方法将封装的密码对象重置为其初始状态
并调用底层输出流的close方法
如果您希望在调用CipherOutputStream.close()方法后仍保持OutputStream打开,则可以将OutputStream包装到不关闭它的流中。例如:
公共类NotClosingOutputStream扩展了OutputStream{
私有最终输出流;
公共NotClosingOutputStream(OutputStream os){
this.os=os;
}
@凌驾
公共无效写入(int b)引发IOException{
os.write(b);
}
@凌驾
public void close()引发IOException{
//没有关闭流。
}
@凌驾
public void flush()引发IOException{
os.flush();
}
@凌驾
公共无效写入(字节[]缓冲区、整数偏移量、整数计数)引发IOException{
写操作(缓冲区、偏移量、计数);
}
@凌驾
公共无效写入(字节[]缓冲区)引发IOException{
写操作(缓冲区);
}
}
然后您可以使用:
。。。
cos=新的CipherOutputStream(新的NotClosingOutputStream(os),encipher);
copyByte(is,cos);
cos.close();
...
请注意,os
流不会关闭,您需要在适当的时候自行关闭。我也看到CipherInputStream因填充问题而失败。这种行为因JVM的不同版本而异。例7u55 32位我的代码运行良好,7u55 64位相同的代码失败。。。我还看到后来的32位JVM出现了故障。解决方法是使用字节数组方法并避免CipherInputStream。不确定这是否与OP的问题有关,但这可能会对某些人有所帮助
当您反复获取java.io.IOException:last block Uncompleted in decryption
时,无论您更改了什么,请检查您是否仍在使用以前运行的文件。如果您的读/写测试代码附加到该文件,您将始终得到该异常,除非您删除写入的损坏文件。使用带填充的CipherInputStream是可能的,切换到NoPadding是一种解决方法,但不是一种解决方案
当CipherInputStream到达流的末尾时应用填充。重要的一点是,为了获取所有数据,必须至少两次调用CipherInputStream的read()方法
以下示例演示如何使用填充读取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()));
}
对于那些正在使用图像文件进行aes加密/解密的人来说,这里是我的示例,它就像一个符咒
public static String decrypt(String textToDecrypt) {
byte[] dataDecrypted = null;
try {
Cipher cipher = getCipher();
SecretKey key = getKey();
IvParameterSpec iv = getIV();
if(cipher == null) {
return null;
}
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] dataToDecrypt = Base64.decode(textToDecrypt, Base64.NO_WRAP);
ByteArrayInputStream bais = new ByteArrayInputStream(dataToDecrypt);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Wrap the output stream
CipherOutputStream cos = new CipherOutputStream(baos, cipher);
// Read bytes
int count = 0;
byte[] buffer = new byte[DEFAULT_BYTE_READ_WRITE_BLOCK_BUFFER_SIZE];
while ((count = bais.read(buffer)) != -1) {
cos.write(buffer, 0, count);
}
cos.close(); // manually do close for the last bit
dataDecrypted = baos.toByteArray();
// Close streams.
baos.close();
bais.close();
} catch (Exception e) {
e.printStackTrace();
}
return (dataDecrypted == null ? "" : Base64.encodeToString(dataDecrypted, Base64.NO_WRAP));
}
密码文本的其余部分可以吗?密码输入流有一个令人讨厌的习惯,就是在地毯下面洗牌异常,包括BadPaddingException
的实例。请检查要写入的文件(?)的大小,并确保写入的所有内容都正确。@owlstead密码的其余部分完全解密,但最后一个块丢失了,要检查我在copyByte
中打印的numbytes
代码,结果是加密时读取所有块,即使最后一个不完整的块也是16位,但对于解密,最后一个块的值不是16,小于16…,所以我猜,它被填充为null或0,因此在加密时读取完整块,因此,加密文件大小与我的原始文件大小相同,不是16字节的倍数…@NikolayElenkov加密文件大小与我的原始文件大小相同,不是16位的倍数…,请参考我之前的评论…尝试调用doFinal()
直接使用密码获取加密的字节并消除任何流问题。您明白为什么吗?或者有一个链接来解释??我也面临着这个问题,我需要链接来理解这个问题是如何解决的。你能给我链接吗?一些算法,如DESeade或AES,需要输入数据具有良好的大小,通常是Cipher.getBlockSize()的倍数。您可以选择在算法中使用填充(“PKCS7Paddinng”)或不使用填充,并且由您确定输入数据的大小是否合适。如果不是,你会得到这个例外。这应该是未来读者对这个问题的公认答案。
public static String encrypt(String textToEncrypt) {
byte[] dataEncrypted = null;
try {
Cipher cipher = getCipher();
SecretKey key = getKey();
IvParameterSpec iv = getIV();
if (cipher == null) {
return null;
}
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] dataToEncrypt = Base64.decode(textToEncrypt, Base64.NO_WRAP);
ByteArrayInputStream bais = new ByteArrayInputStream(dataToEncrypt);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Wrap the output stream
CipherOutputStream cos = new CipherOutputStream(baos, cipher);
// Read bytes
int count = 0;
byte[] buffer = new byte[DEFAULT_BYTE_READ_WRITE_BLOCK_BUFFER_SIZE];
while ((count = bais.read(buffer)) != -1) {
cos.write(buffer, 0, count);
}
cos.close(); // manually do close for the last bit
dataEncrypted = baos.toByteArray();
// Close streams.
baos.close();
bais.close();
} catch (Exception e) {
e.printStackTrace();
}
return (dataEncrypted == null ? "" : Base64.encodeToString(dataEncrypted, Base64.NO_WRAP));
}