使用Cipher与Deflater / Inflater结合使用时丢失字节

时间:2014-06-04 19:37:26

标签: java encryption deflate

我创建了一个执行文件的InputStream - > (MD5) - > Zip - >加密 - > (MD5)和应该反转此操作的OutputStream。但是大多数文件都没有通过测试(参见Main)。尝试使用内容" -abcdefghiz-abcdefghijklmnopqrstuvxyz"的文件。其他人已经描述了类似的问题,但他们忘了关闭流。如果我注释掉Cipher或Deflater / Inflater,它可以正常工作。

import java.io.*;
import java.security.*;
import java.math.*;

import javax.crypto.*;
import javax.crypto.spec.*;

import java.security.spec.*;
import java.util.*;
import java.util.zip.*;

class InputStreamWithZipMd5Aes extends InputStream
{
        InputStream is;
        MessageDigest md = MessageDigest.getInstance("MD5");
        MessageDigest mdEncrytped = MessageDigest.getInstance("MD5");

        public InputStreamWithZipMd5Aes(InputStream innerInputStream, String password) throws Exception
        {
            if (password==null)
                throw new IllegalArgumentException("password");



        byte[] key = password.getBytes("UTF-8");
        MessageDigest sha = MessageDigest.getInstance("SHA-1");
        key = sha.digest(key);
        key = Arrays.copyOf(key, 16); // use only first 128 bit
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");    

        Cipher ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 

        byte[] iv = new byte[]
        {
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
        };

        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);                 
        ecipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, paramSpec); 

        //File -> MD5 -> Zip -> Encrypt -> MD5
        is = new DigestInputStream(innerInputStream, md);
        is = new DeflaterInputStream(is); 
        is = new CipherInputStream(is, ecipher); 
        is = new DigestInputStream(is, mdEncrytped);
    }

    public int read() throws IOException
    {
        return is.read();
    }

    public String getMd5()
    {
        BigInteger bi = new BigInteger(1, md.digest());
        return String.format("%1$032X", bi);
    }

    public String getMd5encrypted()
    {
        BigInteger bi = new BigInteger(1, mdEncrytped.digest());
        return String.format("%1$032X", bi);
    }
}

class OutputStreamWithZipMd5Aes extends OutputStream
{
    Cipher chiper;
    OutputStream os;
    MessageDigest md = MessageDigest.getInstance("MD5");

    public OutputStreamWithZipMd5Aes(OutputStream innerOutpuStream, String password) throws Exception
    {
        if (password==null)
            throw new IllegalArgumentException("password");

        byte[] key = password.getBytes("UTF-8");
        MessageDigest sha = MessageDigest.getInstance("SHA-1");
        key = sha.digest(key);
        key = Arrays.copyOf(key, 16); // use only first 128 bit
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");    

        chiper = Cipher.getInstance("AES/CBC/PKCS5Padding"); 

        byte[] iv = new byte[]
        {
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
        };

        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);                 
        chiper.init(Cipher.DECRYPT_MODE, secretKeySpec, paramSpec); 

        // Decrypt -> Unzip -> MD5 -> File
        os = new DigestOutputStream(innerOutpuStream, md);
        os = new InflaterOutputStream(os); 
        os = new CipherOutputStream(os, chiper);
    }

    public void write(int n) throws IOException
    {
        os.write(n);
    }

    public String getMd5()
    {
        BigInteger bi = new BigInteger(1, md.digest());
        return String.format("%1$032X", bi);
    }

    private static void verifyEncryptDecrypt(File file) throws Exception 
    {
        InputStream inClean = new BufferedInputStream(new FileInputStream(file));
        ByteArrayOutputStream bytesClean = new ByteArrayOutputStream();

        InputStream in = new BufferedInputStream(new FileInputStream(file));
        in = new InputStreamWithZipMd5Aes(in, "password");

        ByteArrayOutputStream bytesProcessed = new ByteArrayOutputStream();
        OutputStreamWithZipMd5Aes os = new OutputStreamWithZipMd5Aes(bytesProcessed, "password");

        byte[] buffer = new byte[1024*1024];

        while (true)
        {
            int read = in.read(buffer);
            if (read == -1)
                break;
            os.write(buffer, 0, read);
        }    

        while (true)
        {
            int read = inClean.read(buffer);
            if (read == -1)
                break;
            bytesClean.write(buffer, 0, read);
        }          

        os.close();
        bytesClean.close();


        System.out.println("#1 " + bytesClean.size() + " : " + bytesClean.toString());
        System.out.println("#2 " + bytesProcessed.size() + " : " + bytesProcessed.toString());
        System.out.println(((InputStreamWithZipMd5Aes)in).getMd5() + " == " + ((OutputStreamWithZipMd5Aes)os).getMd5());
    }

    public static void main(String[] a) throws Exception 
    {
        File file1 = new File("c:\\test.html");
        verifyEncryptDecrypt(file1);
    }
}

输出文件" -abcdefghiz-abcdefghijklmnopqrstuvxyz"

#1 37 : -abcdefghiz-abcdefghijklmnopqrstuvxyz
#2 36 : -abcdefghiz-abcdefghijklmnopqrstuvxy
7F0F45E7EC19AE5F25B6E3F6874B0469 == 39A41A6C429A9010A70C8EA62650F1CD

1 个答案:

答案 0 :(得分:2)

您正在关闭OutputStream的扩展名,但未关闭OutputStreamWithZipMd5Aes包含实际输出流。您必须关闭包含的输出流。

class OutputStreamWithZipMd5Aes extends OutputStream
{
    ...
    OutputStream os;
    ...

    @Override
    public void close() throws IOException
    {
        os.close();
    }
    ...
}

事实上,由于您从未使用过您继承的流(您从未参考OutputStreamWithZipMd5Aes),因此您的课程OutputStream无需延长this

这同样有效:

class OutputStreamWithZipMd5Aes
{
    ...
    OutputStream os;
    ...
    public void write(byte[] buffer, int pos, int n) throws IOException
    {
        os.write(buffer,pos,n);
    }

    public void close() throws IOException
    {
        os.close();
    }
    ...
}