<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Martin&#039;s Weekend Coding &#187; ZIP</title>
	<atom:link href="http://blog.alutam.com/tag/zip/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.alutam.com</link>
	<description>Sharing useful tips from my &#34;weekend projects&#34;</description>
	<lastBuildDate>Sun, 02 Oct 2011 03:15:06 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Reading Password-Protected ZIP Files in Java</title>
		<link>http://blog.alutam.com/2009/10/31/reading-password-protected-zip-files-in-java/#utm_source=feed&#038;utm_medium=feed&#038;utm_campaign=feed</link>
		<comments>http://blog.alutam.com/2009/10/31/reading-password-protected-zip-files-in-java/#comments</comments>
		<pubDate>Sat, 31 Oct 2009 12:13:34 +0000</pubDate>
		<dc:creator>Martin</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Password]]></category>
		<category><![CDATA[ZIP]]></category>

		<guid isPermaLink="false">http://blog.alutam.com/?p=102</guid>
		<description><![CDATA[On a recent &#8220;fun&#8221; project, I needed my application to be able to access password-protected zip files of a particular format. It was one of these features I thought will take me no time to implement. Anyway, to my surprise, neither JDK supports password-protected ZIP files, nor I was able to find a suitable Java [...]]]></description>
			<content:encoded><![CDATA[<p>On a recent &#8220;fun&#8221; project, I needed my application to be able to access password-protected zip files of a particular format. It was one of these features I thought will take me no time to implement. Anyway, to my surprise, neither JDK supports password-protected ZIP files, nor I was able to find a suitable Java open source library I could use for that purpose. So, I ended up writing the utility class on my own. I wrote an implementation of <code>java.io.InputStream</code> that filters the ZIP file data and turns a password-protected ZIP into an unprotected one on the fly &#8211; so the stream can be nicely chained with <code>java.util.zip.ZipInputStream</code>. Although the class is specifically targeted at the particular type of ZIP files I had to deal with (see the limitations below), maybe other people have to deal with the same type of files, or this class can provide a good start for others to turn it into a utility that would work with any type of ZIP (maybe I will do it myself some day &#8211; for now I don&#8217;t have time).<br />
To implement this class I used the <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP File Format Specification</a> as the source of information. I also used the <a href="http://www.7-zip.org/">7-zip project</a> (C++) as a reference during the debugging to verify my understanding of the ZIP spec. and the CRC algorithm.<br />
So, here is the class:</p>
<pre class="brush:java">import java.io.IOException;
import java.io.InputStream;

public class ZipDecryptInputStream extends InputStream {
    private static final int[] CRC_TABLE = new int[256];
    // compute the table
    // (could also have it pre-computed - see http://snippets.dzone.com/tag/crc32)
    static {
        for (int i = 0; i &lt; 256; i++) {
            int r = i;
            for (int j = 0; j &lt; 8; j++) {
                if ((r &amp; 1) == 1) {
                    r = (r &gt;&gt;&gt; 1) ^ 0xedb88320;
                } else {
                    r &gt;&gt;&gt;= 1;
                }
            }
            CRC_TABLE[i] = r;
        }
    }

    private static final int DECRYPT_HEADER_SIZE = 12;
    private static final int[] LFH_SIGNATURE = {0x50, 0x4b, 0x03, 0x04};

    private final InputStream delegate;
    private final String password;
    private final int keys[] = new int[3];

    private State state = State.SIGNATURE;
    private int skipBytes;
    private int compressedSize;
    private int value;
    private int valuePos;
    private int valueInc;

    public ZipDecryptInputStream(InputStream stream, String password) {
        this.delegate = stream;
        this.password = password;
    }

    @Override
    public int read() throws IOException {
        int result = delegate.read();
        if (skipBytes == 0) {
            switch (state) {
                case SIGNATURE:
                    if (result != LFH_SIGNATURE[valuePos]) {
                        state = State.TAIL;
                    } else {
                        valuePos++;
                        if (valuePos &gt;= LFH_SIGNATURE.length) {
                            skipBytes = 2;
                            state = State.FLAGS;
                        }
                    }
                    break;
                case FLAGS:
                    if ((result &amp; 1) == 0) {
                        throw new IllegalStateException("ZIP not password protected.");
                    }
                    if ((result &amp; 64) == 64) {
                        throw new IllegalStateException("Strong encryption used.");
                    }
                    if ((result &amp; 8) == 8) {
                        throw new IllegalStateException("Unsupported ZIP format.");
                    }
                    result -= 1;
                    compressedSize = 0;
                    valuePos = 0;
                    valueInc = DECRYPT_HEADER_SIZE;
                    state = State.COMPRESSED_SIZE;
                    skipBytes = 11;
                    break;
                case COMPRESSED_SIZE:
                    compressedSize += result &lt;&lt; (8 * valuePos);
                    result -= valueInc;
                    if (result &lt; 0) {
                        valueInc = 1;
                        result += 256;
                    } else {
                        valueInc = 0;
                    }
                    valuePos++;
                    if (valuePos &gt; 3) {
                        valuePos = 0;
                        value = 0;
                        state = State.FN_LENGTH;
                        skipBytes = 4;
                    }
                    break;
                case FN_LENGTH:
                case EF_LENGTH:
                    value += result &lt;&lt; 8 * valuePos;
                    if (valuePos == 1) {
                        valuePos = 0;
                        if (state == State.FN_LENGTH) {
                            state = State.EF_LENGTH;
                        } else {
                            state = State.HEADER;
                            skipBytes = value;
                        }
                    } else {
                        valuePos = 1;
                    }
                    break;
                case HEADER:
                    initKeys(password);
                    for (int i = 0; i &lt; DECRYPT_HEADER_SIZE; i++) {
                        updateKeys((byte) (result ^ decryptByte()));
                        result = delegate.read();
                    }
                    compressedSize -= DECRYPT_HEADER_SIZE;
                    state = State.DATA;
                    // intentionally no break
                case DATA:
                    result = (result ^ decryptByte()) &amp; 0xff;
                    updateKeys((byte) result);
                    compressedSize--;
                    if (compressedSize == 0) {
                        valuePos = 0;
                        state = State.SIGNATURE;
                    }
                    break;
                case TAIL:
                    // do nothing
            }
        } else {
            skipBytes--;
        }
        return result;
    }

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

    private void initKeys(String password) {
        keys[0] = 305419896;
        keys[1] = 591751049;
        keys[2] = 878082192;
        for (int i = 0; i &lt; password.length(); i++) {
            updateKeys((byte) (password.charAt(i) &amp; 0xff));
        }
    }

    private void updateKeys(byte charAt) {
        keys[0] = crc32(keys[0], charAt);
        keys[1] += keys[0] &amp; 0xff;
        keys[1] = keys[1] * 134775813 + 1;
        keys[2] = crc32(keys[2], (byte) (keys[1] &gt;&gt; 24));
    }

    private byte decryptByte() {
        int temp = keys[2] | 2;
        return (byte) ((temp * (temp ^ 1)) &gt;&gt;&gt; 8);
    }

    private int crc32(int oldCrc, byte charAt) {
        return ((oldCrc &gt;&gt;&gt; 8) ^ CRC_TABLE[(oldCrc ^ charAt) &amp; 0xff]);
    }

    private static enum State {
        SIGNATURE, FLAGS, COMPRESSED_SIZE, FN_LENGTH, EF_LENGTH, HEADER, DATA, TAIL
    }
}</pre>
<p>These are the limitations:</p>
<ul>
<li>Only the &#8220;Traditional PKWARE Encryption&#8221; is supported (spec. section VII)</li>
<li>Files that have the &#8220;compressed length&#8221; information at the end of the data section (rather than at the beginning) are not supported (see &#8220;general purpose bit flag&#8221;, bit 3 in section V, subsection J in the spec.)</li>
</ul>
<p>And this is how you can use it in your code:</p>
<pre class="brush:java">import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

// usage: java Main [filename] [password]
public class Main {
    public static void main(String[] args) throws IOException {
        // password-protected zip file I need to read
        FileInputStream fis = new FileInputStream(args[0]);
        // wrap it in the decrypt stream
        ZipDecryptInputStream zdis = new ZipDecryptInputStream(fis, args[1]);
        // wrap the decrypt stream by the ZIP input stream
        ZipInputStream zis = new ZipInputStream(zdis);

        // read all the zip entries and save them as files
        ZipEntry ze;
        while ((ze = zis.getNextEntry()) != null) {
            FileOutputStream fos = new FileOutputStream(ze.getName());
            int b;
            while ((b = zis.read()) != -1) {
                fos.write(b);
            }
            fos.close();
            zis.closeEntry();
        }
        zis.close();
    }
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.alutam.com/2009/10/31/reading-password-protected-zip-files-in-java/feed/</wfw:commentRss>
		<slash:comments>54</slash:comments>
		</item>
	</channel>
</rss>

