Stig
Stig

Reputation: 2086

Losing bytes when using Cipher combined with Deflater/Inflater

I have created an InputStream that does File -> (MD5) -> Zip -> Encrypt -> (MD5) and a OutputStream that should reverse this action. But most files fails the test (see Main). Try a file with content "-abcdefghiz-abcdefghijklmnopqrstuvxyz". Others have described similar problems, but they had forgot to close the stream. If i comment out the Cipher or the Deflater/Inflater it works fine.

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);
    }
}

Outputs with file "-abcdefghiz-abcdefghijklmnopqrstuvxyz"

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

Upvotes: 0

Views: 250

Answers (1)

Jim Garrison
Jim Garrison

Reputation: 86774

You are closing YOUR extension of OutputStream but not closing the actual output stream which is contained within OutputStreamWithZipMd5Aes. You must close the contained output stream.

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

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

In fact, there's no reason your class OutputStreamWithZipMd5Aes has to extend OutputStream since you never use the stream you inherit (you never refer to this).

This works just as well:

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();
    }
    ...
}

Upvotes: 2

Related Questions