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(); } }
Very cool. Thanks
Thanks. This class is just what I needed.
What zip softwares do still use pkware encryption???
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? :)
Heather, this class cannot handle the AES256 encryption – just the basic pkware encryption that was used in earlier versions of ZIP files.
How do I know if the password is correct?
Sorry for my English!
Thanks :)
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.
Could you post code for this case?
Thank you very much!
Is there an ZipDecryptOutputStream class too?
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.
hi, thankyou, i’ll try this one
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… :)
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!!!
Thank you for posting this as it appears to be working perfectly. Within minutes I was unzipping these files programmatically.
THX,It’s very useful
Hi., this is great coding. Its very useful for me. Thanks
there is the parts of the code with password test:
case CRC:
crc[3] = (byte) (result & 0xFF);
crc[2] = (byte) ((result >> 8) & 0xFF);
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
Hi Martin,
Thanks your codes.
One question:
case CRC:
crc[3] = (byte) (result & 0xFF);
crc[2] = (byte) ((result >> & 0xFF);
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!”);
I run it and crc[0~2] always are Zero.
“throw new …” will not be called.
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
how to run class file which is inside password protected zip file
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.
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.
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.
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. :)
Use this for a better performance:
FileOutputStream fos = new FileOutputStream(destinationDir+”\\”+ze.getName());
int BUFFER = 2048;
byte data [ ] = new byte [ BUFFER ];
int count;
while ((count = zis.read(data, 0, BUFFER)) != -1){
fos.write(data, 0, count);
}
hi martin,
can above code be implemented in c#.
I’m not a C# expert. Not sure what libraries in .NET are available for handling ZIP files.
ICSharpCode.SharpZipLib
actually i am able to unzip zip file but when a password protected zip file is encountered , it fails to unzip it.
how unzip if i do not know the password.
What you writed is excellent!Thank you !
Hi, how to make a protected zip ?
any suggestion about that ?
Hi Martin,
First of all – thanks for the sample. Small question for you. Did you try to unzip file with multiple entities and one of them has size 0? I tried it out and unziping works fine until it gets to the empty file then process does not recognize next entity and exits.
Appreciate your help.
Hi Dmitry, no, I haven’t tried that. If you could send me such zip file, I can try to fix the code.
Martin
Hi Martin,
verifying the program found that does not decompress when you have multiple files, I’m trying to validate the code, but still do not find that can happen, thanks for this excellent source
I did use it with zip containing multiple files – so it should work. But as Dmitry pointed out, there may be an issue when one or more files are zero-length.
To be able to work with zero-length files just change the “case HEADER” block with:
case HEADER:
initKeys(password);
for (int i = 0; i = LFH_SIGNATURE.length) {
skipBytes = 2;
state = State.FLAGS;
}
}
break;
}
// intentionally no break
Hi Martin
This code is not working correctly when we have a 0 kb file in zip.
Can you please suggest.
Many thanks for posting this. Works like a charm, and saves me lots of time. Cheers.
Thx, realy much. this is awesome class.
Hi Martin,
can i use your class for my project without any restrictions?
Cheers
Asterios
Yes.
Thanks Martin! When i will use your class i will put a link to your blog here.
Cheers
Asterios
I read the code and I surprised, that’s it I am looking for.
Sorry, I forgot one thing Thanks and thanks for your work that you have done.
thanks for saving me the time!!
Hi , Martin
I ‘m try to write simple program for brute-force password in zip in Operating System Subject. First program for thread
I flow this appnote to decrypt header (12 first byte)
http://www.pkware.com/documents/casestudies/APPNOTE.TXT
Header decrypt with no problem. but I ‘m not understand this message
“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.”
I ‘m try to put correct password. But last decrypted header value not match with
CRC Value (14 – 17 byte position).
Here this code. Write in C++
[code]
#include
#include
#include
#include
#include
#include “pthread.h”
unsigned int crc(unsigned int x , unsigned char b)
{
x = x ^ (unsigned long)b;
for(int i=0; i > 1) ^ 0xEDB88320;
else
x = (x >> 1);
}
return x;
}
int main()
{
FILE *fp;
char zip_location[] = “D:\\Zip\\test_pass.zip”;
unsigned char password[] = “1234”;
unsigned char header_buffer[12];
unsigned char crc_file[25];
unsigned int keys[3];
unsigned int crc_decrypt;
printf(“\nFile = %s\n\n”,zip_location);
// First Init 96 Bit Internal State
keys[0] = 0x12345678;
keys[1] = 0x23456789;
keys[2] = 0x34567890;
// Step 1 – Initializing the encryption keys
for(int i=0; i > 24));
}
// Step 2 – Decrypting the encryption header
fp = fopen (zip_location,”r+b”);
if(fp == NULL)
{
printf(“Error! Can’t open file\n”);
}
else
{
fread(header_buffer,1,12,fp);
// Jump to CRC32 Section
fseek(fp,14,SEEK_SET);
if(fread(crc_file, 1,4,fp))
{
printf(“CRC From File = %X %X %X %X \n\n”,crc_file[3],crc_file[2],crc_file[1],crc_file[0]);
}
fclose(fp);
}
for(int i=0; i> 8);
keys[0] = crc(keys[0],c);
keys[1] = keys[1] + (keys[0] & 0x000000FF);
keys[1] = keys[1] * 134775813 + 1;
keys[2] = crc(keys[2],keys[1] >> 24);
header_buffer[i] = c;
}
for(int i=0; i<12; i++)
printf("Buffer[%d]\t= \t0x%X\n",i,header_buffer[i]);
getch();
return 0;
}
[/code]
My email : sum.developer [ a+t ] gmail.com
What does the second limitation means , or how could I get the correct zip files ?
Hi everyone I implemented that in c but I think there is a problem with password verification.
If I’m not mistaken based on pkware we only use 1 byte to check the crc. So we decrypt the file header that is 12 bytes and take it’s last byte and compare it with the crc value that we got from the CRC32 from Local file header so we have 1 byte that can get 255 values comparing with a random value that we got after decrypting the file header that other byte can also take 255 values.
But the passwords that we can use are infinite so there should be many different passwords that might return the same crc value so that means that i might use a wrong password and that crc check will return that thee pass is correct! So it can’t be only that 1 byte check it should be something more…. anyone has an idea how exactly it verify the password?
Hi Martin
Congratulations! your program has become a defacto standard to decrypt password-protected zip files.
But my counterparts are expecting me now to send them PKWare encrypted zip files, after an extensive search I could find java programs which uses AES encryption but not PKWare.
I am in gr8 need of ZipEncryptOutputStream :(
Any help ??
Well, I have been thinking for some time I’d create an open source project for this and add the output stream as well – as I do see this simple post on my blog is getting lots of hits – so seems like it is quite useful. The problem with the output stream is that I won’t be able to do it in a really streaming way because of the way how Java Zip output stream encodes the file – will have to read significant portion of the zip content into memory – so you will have to make sure your zip file is of a reasonable size or that you allocate enough heap. Will see if I get some time over the weekend… :)
Finally the ZipEncryptOutputStream was born. See my new blog entry: http://blog.alutam.com/2012/03/31/new-library-for-reading-and-writing-zip-files-in-java/
Hi Martin!
Thank you very much for your interesting blog!
We think about integrating your source code posted inside this blog entry “Reading Password-Protected ZIP Files in Java” into one of our applications intended to be finally used for production scenarios. Could you please let us know if you have any concerns regarding this approach? Please let us know if there are terms of a license or any other restrictions we might be aware of?
Your help is much appreciated!
Kind Regards,
Chris
You can use it in any way you want. If you are re-publishing the source code, referring back to this blog in a javadoc comment would be nice.
Thank you, it’s great :-)
Hello Martin sir,
I have a zip 2.0 file named myprg.zip, password is slip in my mind . I want to open it but I does not know about java . Sir I am sending you our zip file pls open it & I want to know how your class is working. Please send me full process file and class who is open myprg.zip file & how is run.
Thanking you
hi,
I know this is a repetition…..However, its my job to do so……will you please let me use this code snippet in a commercial and closed-source software?
Yes, you can use it in a commercial and closed-source sw.
hi, i hav executed this code, i am getting exception like this: Exception in thread “main” java.lang.IllegalStateException: Unsupported ZIP format.
at ZipDecryptInputStream.read(ZipDecryptInputStream.java:65)
at java.io.InputStream.read(Unknown Source)
at java.io.FilterInputStream.read(Unknown Source)
at java.io.PushbackInputStream.read(Unknown Source)
at java.util.zip.ZipInputStream.readFully(Unknown Source)
at java.util.zip.ZipInputStream.readLOC(Unknown Source)
at java.util.zip.ZipInputStream.getNextEntry(Unknown Source)
at Main.main(Main.java:19)
Should i follow any special technique to zip the files? pls reply
Hi,
I ran your code in android but it gives fileNotFound Exception when I am creating FileOutputStream at
FileOutputStream fos = new FileOutputStream(ze.getName());
.
My pwd protected zip contains many zip files. Is there any manipulation required in the code in my case?
Please reply.
Perhaps you are trying to create a file in a non-existent directory? See how the name of the file looks like and if such directory exists. Try creating that directory before creating the output stream. Just a guess.
Awesome, ty. “r = (r >>> 1) ^ 0xedb88320;”, lol comedy gold.
Hello Martin,
thanks for that code, solved my problem just fine. One small issue: I had to unzip a few hundred zip-files of a few hundred MB each. And since your stream-class reads byte by byte, it took ages.
I added the following method and variables and replaced the calls to “delegate.read()” with “read(delegate)”. It’s only a minor change, but it increased unzipping-speed by almost a factor of 100.
static private final int BUF_LEN = 1<=BUF_FIL) {
BUF_IDX=0;
BUF_FIL=aStream.read(BUFFER);
}
return BUFFER[BUF_IDX++]&0xFF;
}
I did not want to duplicate the functionality of BufferedInputStream and thought people should rather wrap the original input stream in the BufferedInputStream before they pass it to the ZipDecryptInputStream. Have you tried that?
Oops, sorry, that code got mutilated due to the “larger-than” and “smaller-than” signs.
Next try:
static private final int BUF_LEN = 1<<20;
static private final byte[] BUFFER = new byte[BUF_LEN];
static private int BUF_IDX = 0;
static private int BUF_FIL = 0;
public int read(InputStream aStream)
throws IOException {
if(BUF_IDX>=BUF_FIL) {
BUF_IDX=0;
BUF_FIL=aStream.read(BUFFER);
}
return BUFFER[BUF_IDX++]&0xFF;
}
Hello Hendrik,
thanks for your suggestion to increasing unzipping-speed .I have a 30 MB zip file,and I use your suggestion to unzip it.It takes me about 20 seconds.it is a little long time,could you give me some other suggestion to increase unzipping-speed.
thank you
Hi martin, when i used this code for unzip protection password… i got some excemption like this….
java.lang.IllegalStateException: Unsupported ZIP format.
at ZipDecryptInputStream.read(ZipDecryptInputStream.java:65)
at java.io.InputStream.read(Unknown Source)
at java.io.FilterInputStream.read(Unknown Source)
at java.io.PushbackInputStream.read(Unknown Source)
at java.util.zip.ZipInputStream.readFully(Unknown Source)
at java.util.zip.ZipInputStream.readLOC(Unknown Source)
at java.util.zip.ZipInputStream.getNextEntry(Unknown Source)
at Main.main(Main.java:19)
Can U help me plz…i tried alot….
Hi,
your code in android but it gives fileNotFound Exception at
FileOutputStream fos = new FileOutputStream(ze.getName());
.
My asswordprotected zip contains many zip files. Is there any changerequired in the code ?
Please reply.
Thanks a lot. It worked like a charm
i am getting following error while running the above code can any one help me to resolve this issue.
Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: 1
at TestReadingZipFile.main(TestReadingZipFile.java:16)
bcoz of commandline args .
need help asap
Thanks&Regards,
sivakumar
this is gem
although ill be using zip4j but this is the real art
Hi,
Thanks for the post
But I am trying to encrypt a zip file using tools and is there any tool to encrypt the file using Traditional PKWARE Encryption.
http://blog.alutam.com/2012/03/31/new-library-for-reading-and-writing-zip-files-in-java/
Please Help…
getting This Exception
Exception in thread “main” java.util.zip.ZipException: too many length or distance symbols
at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:164)
at java.util.zip.ZipInputStream.read(ZipInputStream.java:193)
at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:122)
at swingGUI.Main.main(Main.java:30)
Java Result: 1
BUILD SUCCESSFUL (total time: 0 seconds)
Please can you look into this the zip with subdirectory is not working with this code
It is now fixed in https://bitbucket.org/matulic/ziputils
Hello,
l’ve a problem with zero-byte files
I’m using version 1.3.
Di
Where in the code will I input the name of the protected file I want to read
Any answer, please…
The code of the main method assumes you will pass it as the first parameter on the command line. If you want to hard-code the name, you can replace “args[0]” on line 11 with the actual name and args[1] on line 13 with the password.