Problem:

We have created a module which digitally signs our customer’s documents and then automatically wrap them inside one zip and send it to interested parties. Module was working properly and we were happy but then all of sudden we got a report from some of the interested parties that our digitally signed documents are containing invalid signatures which was not the case before. We have been presented with the problem of sorting out why this is happening and workaround for this issue.

Findings:

We  started looking for an issue from the file itself, we opened file in the Adobe Reader and it presented us with message box saying that

   “Digitally Sign is invalid due to bytes of the documents have been changed since last time it was signed”

That gave us the clue that, it may be issue with some processing in our module which changes bytes of the signed document. We analyzed our module to identify the processes which may be writing and modifiying signed docs. After investing some time, we found out that its the issue with Zip Compression process in which Zip algorithm compresses given files to wrap them in small size block. Now as we were able to identify that issue is not with the digitally signed files but with the files which are in the zip., we lay out possible solutions which we can implie to fix it.

Its worth pointing that we were using,  jdk 7’s shipped zip processing classes, iText for pdf manipulation and e-Mudra service for digitally signing documents.

  1. First solution was to send files as individual attachment in the mail, but that was not possible as files were too many and it may create issues for user afterwards if he/she wants to transfer files from mail attachment to some other storage. Also, many of the mail client offers ability to download all the attached files in one zip, so that will again cause problem.
  2. Second solution was to somehow compress files into zip with compression ratio set to the 0%.

Out of above two possible solution, we agreed to use second one and we started to find way in JDK7’s zip compression api which can offer us uncompresses zip file creation.

After bit search over internet we found that its possible to zip files with 0% compression ratio, and that was possible by tweaking a bit how zip is created using ZipOutputStream.

Here is how you an compress zip with 0% compression ratio.

 



import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Uncompressed Zip Creator. 
 * @author Milan Z. Savaliya
 */
public class UncompressedZipCreator {

    private ZipOutputStream zipOutputStream;
    private final String finalZipOutputPath;
    private final String[] filePathArray;

    public UncompressedZipCreator(String finalZipOutputPath, String[] filePathArry) {
        this.finalZipOutputPath = finalZipOutputPath;
        this.filePathArray = filePathArry;
    }

    private void initZipOutputStream() throws FileNotFoundException {
        FileOutputStream foForxml = new FileOutputStream(this.finalZipOutputPath);
        this.zipOutputStream = new ZipOutputStream(foForxml);
        //This will ensure us that files get zipped without compression
        this.zipOutputStream.setMethod(ZipOutputStream.STORED);
    }

    public void createZip() throws IOException {
        initZipOutputStream();
        for (String fileName : this.filePathArray) {
            addToZipFile(fileName);
        }
        this.zipOutputStream.close();
    }

    private void addToZipFile(String fileName) throws IOException {
        File file = new File(fileName);
        try (FileInputStream fis = new FileInputStream(file)) {
            ZipEntry zipEntry = new ZipEntry(file.getName());

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0) {
                baos.write(bytes, 0, length);
            }

            // need to set size and CRC when using STORED method
            zipEntry.setSize(baos.size());

            CRC32 crc32 = new CRC32();
            crc32.update(baos.toByteArray());
            zipEntry.setCrc(crc32.getValue());

            this.zipOutputStream.putNextEntry(zipEntry);
            this.zipOutputStream.write(baos.toByteArray());

            this.zipOutputStream.closeEntry();
        }
    }
}

One thing to notice here is that when using



zipOutputStream.setMethod(ZipOutputStream.STORED) // method option which allows us to achive uncompressed zip creation

we would need to calculate each file’s total byte size and its data’s CSR32 checksum. If we do not do that then we are welcoming error

java.util.zip.ZipException: STORED entry missing size, compressed size, or crc-32.

So its required to do this additional step for each file we want to add.

 

And, Here is how we can use UncompressZipCreator


package com.milansvaliya.file;

import java.io.IOException;

public class UncompressedZipCreatorDemo {

    public static void main(String[] args) {
        String zipOutputPath = "/opt/temp_files/uncompressed.zip";
        String[] fileToIncludeArray = new String[]{
            "/opt/temp_files/doc1.pdf",
            "/opt/temp_files/doc2.pdf",
            "/opt/temp_files/doc3.pdf",
            "/opt/temp_files/doc4.pdf",};

        UncompressedZipCreator zipCreator = new UncompressedZipCreator(zipOutputPath, fileToIncludeArray);
        try {
            zipCreator.createZip();
            System.out.println("Uncompressed Zip Created at " + zipOutputPath);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

}