On a recent “fun” 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 java.io.InputStream that filters the ZIP file data and turns a password-protected ZIP into an unprotected one on the fly – so the stream can be nicely chained with java.util.zip.ZipInputStream. 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 – for now I don’t have time).
To implement this class I used the ZIP File Format Specification as the source of information. I also used the 7-zip project (C++) as a reference during the debugging to verify my understanding of the ZIP spec. and the CRC algorithm.
So, here is the class:
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 < 256; i++) { int r = i; for (int j = 0; j < 8; j++) { if ((r & 1) == 1) { r = (r >>> 1) ^ 0xedb88320; } else { r >>>= 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 >= LFH_SIGNATURE.length) { skipBytes = 2; state = State.FLAGS; } } break; case FLAGS: if ((result & 1) == 0) { throw new IllegalStateException("ZIP not password protected."); } if ((result & 64) == 64) { throw new IllegalStateException("Strong encryption used."); } if ((result & 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 << (8 * valuePos); result -= valueInc; if (result < 0) { valueInc = 1; result += 256; } else { valueInc = 0; } valuePos++; if (valuePos > 3) { valuePos = 0; value = 0; state = State.FN_LENGTH; skipBytes = 4; } break; case FN_LENGTH: case EF_LENGTH: value += result << 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 < 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()) & 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 < password.length(); i++) { updateKeys((byte) (password.charAt(i) & 0xff)); } } private void updateKeys(byte charAt) { keys[0] = crc32(keys[0], charAt); keys[1] += keys[0] & 0xff; keys[1] = keys[1] * 134775813 + 1; keys[2] = crc32(keys[2], (byte) (keys[1] >> 24)); } private byte decryptByte() { int temp = keys[2] | 2; return (byte) ((temp * (temp ^ 1)) >>> 8); } private int crc32(int oldCrc, byte charAt) { return ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff]); } private static enum State { SIGNATURE, FLAGS, COMPRESSED_SIZE, FN_LENGTH, EF_LENGTH, HEADER, DATA, TAIL } }
These are the limitations:
- Only the “Traditional PKWARE Encryption” is supported (spec. section VII)
- Files that have the “compressed length” information at the end of the data section (rather than at the beginning) are not supported (see “general purpose bit flag”, bit 3 in section V, subsection J in the spec.)
And this is how you can use it in your code:
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(); } }




Posts
Very cool. Thanks
| November 19, 2009 @ 12:24 am
Thanks. This class is just what I needed.
| December 9, 2009 @ 1:53 pm
What zip softwares do still use pkware encryption???
| January 13, 2010 @ 7:11 pm
This is great and a time saver. however, I’m getting an invalid compression method when the ZipInputStream is trying to read my file. It’s using pkzip format and AES256 bit encryption. It seems like I shouldn’t need to change anything, but I don’t know. If anyone could help or offer input?
| January 20, 2010 @ 8:21 pm
Heather, this class cannot handle the AES256 encryption – just the basic pkware encryption that was used in earlier versions of ZIP files.
| February 3, 2010 @ 1:49 am
How do I know if the password is correct?
Sorry for my English!
Thanks
| January 28, 2010 @ 3:10 pm
Hi,
the ZIP spec. says:
“After the header is decrypted, the last 1 or 2 bytes in Buffer
should be the high-order word/byte of the CRC for the file being
decrypted, stored in Intel low-byte/high-byte order. Versions of
PKZIP prior to 2.0 used a 2 byte CRC check; a 1 byte CRC check is
used on versions after 2.0. This can be used to test if the password
supplied is correct or not.”
In the code from my blog, you could do this check at the end of the HEADER case branch. To compare the header bytes with the CRC of the file, you would have to add another branch for reading the CRC as well – CRC of the file are 4 bytes just before the COMPRESSED_SIZE section of the zip file.
| February 3, 2010 @ 2:07 am
Is there an ZipDecryptOutputStream class too?
| January 29, 2010 @ 1:59 pm
Do you mean ZipEncryptOutputStream, that would password protect the zip? No, that one I did not do. I did not need it and ZipOutputStream does things the way that makes it a little bit harder to implement similarly simple output stream for encryption.
| February 3, 2010 @ 1:52 am
hi, thankyou, i’ll try this one
| February 18, 2010 @ 11:30 am
hello sir,
i h’d successed in decrypting,
but, may i know how the code for creating zip file with password in java
thankyou much…
| February 18, 2010 @ 7:11 pm
Hi,
The code was greate.
I now working a project need to create password protected zip file, that can open in winxp.
But there is limited information about zip with Zip 2.0 Encryption/Traditional PKWARE Encryptin.
I just wonder if there any code for creating password protected zip file with Zip 2.0 Encryption/Traditional PKWARE Encryptin.
Or it there any information about this subject can i refer to?
Thank!!!
| March 15, 2010 @ 8:53 am
Thank you for posting this as it appears to be working perfectly. Within minutes I was unzipping these files programmatically.
| March 19, 2010 @ 9:53 pm
THX,It’s very useful
| April 15, 2010 @ 5:14 pm
there is the parts of the code with password test:
case CRC:
& 0xFF);
crc[3] = (byte) (result & 0xFF);
crc[2] = (byte) ((result >>
crc[1] = (byte) ((result >> 16) & 0xFF);
crc[0] = (byte) ((result >> 24) & 0xFF);
if(crc[2] > 0 || crc[1] > 0 || crc[2] > 0)
throw new IllegalStateException(“CRC with more than 1 byte, WTF!”);
state = State.COMPRESSED_SIZE;
break;
case HEADER:
initKeys(password);
for (int i = 0; i < DECRYPT_HEADER_SIZE; i++) {
if(i+1 == DECRYPT_HEADER_SIZE && ((byte)(result ^ decryptByte()) != crc[3]))
throw new IllegalStateException("Wrong password!");
updateKeys((byte) (result ^ decryptByte()));
result = delegate.read();
}
Code complete on http://pastebin.com/tDk57eex
| May 10, 2010 @ 2:23 am
hi,I have to handle a zip file with zlib format,DEFLATE,tranditional PKWARE Encryption .
could you give me some suggestion?
thanks,sorry for my english
| June 9, 2010 @ 12:11 pm
how to run class file which is inside password protected zip file
| June 23, 2010 @ 8:41 am
I guess you would have to write your own ClassLoader implementation that loads classes in password-protected zips. You’d still need to have this bootstrapping code (the ClassLoader implementation) in a non-protected jar or class file.
| June 23, 2010 @ 10:37 am
Hi! I used your code for my android project. It works for me but it is very slow. This is my code:
FileInputStream fis = new FileInputStream(f);
ZipDecryptInputStream zdis = new ZipDecryptInputStream(fis,”letmelearn~!@#$%”);
ZipInputStream zis = new ZipInputStream(zdis);
ZipEntry ze;
while((ze = zis.getNextEntry()) != null){
if(ze.getName().equals(“0001_2_01.jpg){
Bitmap bm = BitmapFactory.decodeStream(zis); // this takes over 3 minutes to continue
break;
}
}
So, I tried the code you posted here and not changing anything but it still very slow. Do you know what causes this problem? Thanks in advance.
| June 28, 2010 @ 12:48 pm
Please try using a plain ZipInputStream with an unencrypted zip file (same content, but no password) and see how fast/slow is that. In case it is much faster, you can try to see if you can optimize my ZipDecryptInputStream with a profiler. In case it is similarly slow reading unencrypted zip files with plain ZipInputStream, looks like you will have to find some other way of unzipping archives.
| June 28, 2010 @ 12:57 pm
Yes, I have already tried unzipping unencrypted zip file and it is fast. This should be fast because most of my zip files have 3 to 4 files inside. I’ll try to look up your code and see if I can make some adjustments. Thanks for the code by the way.
| June 28, 2010 @ 1:04 pm
hi martin,
can above code be implemented in c#.
| July 3, 2010 @ 9:08 am
I’m not a C# expert. Not sure what libraries in .NET are available for handling ZIP files.
| July 13, 2010 @ 3:17 pm
ICSharpCode.SharpZipLib
| July 29, 2010 @ 2:40 pm
actually i am able to unzip zip file but when a password protected zip file is encountered , it fails to unzip it.
| July 3, 2010 @ 9:10 am
how unzip if i do not know the password.
| July 3, 2010 @ 1:27 pm
What you writed is excellent!Thank you !
| July 5, 2010 @ 5:24 am