All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.alpharogroup.file.zip.ZipDecryptInputStream Maven / Gradle / Ivy

There is a newer version: 5.7
Show newest version
/**
 * The MIT License
 *
 * Copyright (C) 2007 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.file.zip;

import java.io.IOException;
import java.io.InputStream;

// from 'http://blog.alutam.com/2009/10/31/reading-password-protected-zip-files-in-java/'
/**
 * The Class ZipDecryptInputStream is from the blog from Martin's Weekend Coding from
 * 'http://blog.alutam.com/2009/10/31/reading-password-protected-zip-files-in-java/'.
 */
public class ZipDecryptInputStream extends InputStream
{

	/**
	 * The Enum State.
	 */
	private static enum State
	{

		/** The SIGNATURE. */
		SIGNATURE,
		/** The FLAGS. */
		FLAGS,
		/** The COMPRESSE d_ size. */
		COMPRESSED_SIZE,
		/** The F n_ length. */
		FN_LENGTH,
		/** The E f_ length. */
		EF_LENGTH,
		/** The HEADER. */
		HEADER,
		/** The DATA. */
		DATA,
		/** The TAIL. */
		TAIL
	}

	/** The Constant CRC_TABLE. */
	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;
		}
	}

	/** The Constant DECRYPT_HEADER_SIZE. */
	private static final int DECRYPT_HEADER_SIZE = 12;

	/** The Constant LFH_SIGNATURE. */
	private static final int[] LFH_SIGNATURE = { 0x50, 0x4b, 0x03, 0x04 };

	/** The delegate. */
	private final InputStream delegate;

	/** The password. */
	private final String password;

	/** The keys. */
	private final int keys[] = new int[3];

	/** The state. */
	private State state = State.SIGNATURE;

	/** The skip bytes. */
	private int skipBytes;

	/** The compressed size. */
	private int compressedSize;

	/** The value. */
	private int value;

	/** The value pos. */
	private int valuePos;

	/** The value inc. */
	private int valueInc;

	/**
	 * Instantiates a new zip decrypt input stream.
	 *
	 * @param stream
	 *            the stream
	 * @param password
	 *            the password
	 */
	public ZipDecryptInputStream(final InputStream stream, final String password)
	{
		this.delegate = stream;
		this.password = password;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.InputStream#close()
	 */
	@Override
	public void close() throws IOException
	{
		delegate.close();
		super.close();
	}

	/**
	 * Crc32.
	 *
	 * @param oldCrc
	 *            the old crc
	 * @param charAt
	 *            the char at
	 * @return the int
	 */
	private int crc32(final int oldCrc, final byte charAt)
	{
		return oldCrc >>> 8 ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff];
	}

	/**
	 * Decrypt byte.
	 *
	 * @return the byte
	 */
	private byte decryptByte()
	{
		final int temp = keys[2] | 2;
		return (byte)(temp * (temp ^ 1) >>> 8);
	}

	/**
	 * Inits the keys.
	 *
	 * @param password
	 *            the password
	 */
	private void initKeys(final 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));
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.InputStream#read()
	 */
	@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;
	}

	/**
	 * Update keys.
	 *
	 * @param charAt
	 *            the char at
	 */
	private void updateKeys(final 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));
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy