Implementation of ZipCrypto / Zip 2.0 encryption in java

Posted by gomesla on Stack Overflow See other posts from Stack Overflow or by gomesla
Published on 2009-10-30T18:10:20Z Indexed on 2010/06/03 4:34 UTC
Read the original article Hit count: 1559

Filed under:
|
|

I'm trying o implement the zipcrypto / zip 2.0 encryption algoritm to deal with encrypted zip files as discussed in

http://www.pkware.com/documents/casestudies/APPNOTE.TXT

I believe I've followed the specs but just can't seem to get it working. I'm fairly sure the issue has to do with my interpretation of the crc algorithm.

The documentation states

CRC-32: (4 bytes)

The CRC-32 algorithm was generously contributed by
David Schwaderer and can be found in his excellent
book "C Programmers Guide to NetBIOS" published by
Howard W. Sams & Co. Inc.  The 'magic number' for
the CRC is 0xdebb20e3.  The proper CRC pre and post
conditioning is used, meaning that the CRC register
is pre-conditioned with all ones (a starting value
of 0xffffffff) and the value is post-conditioned by
taking the one's complement of the CRC residual.

Here is the snippet that I'm using for the crc32

public class PKZIPCRC32 {
	private static final int	CRC32_POLYNOMIAL	= 0xdebb20e3;
	private int					crc					= 0xffffffff;
	private int					CRCTable[];

	public PKZIPCRC32() {
		buildCRCTable();
	}

	private void buildCRCTable() {
		int i, j;
		CRCTable = new int[256];
		for (i = 0; i <= 255; i++) {
			crc = i;
			for (j = 8; j > 0; j--)
				if ((crc & 1) == 1)
					crc = (crc >>> 1) ^ CRC32_POLYNOMIAL;
				else
					crc >>>= 1;
			CRCTable[i] = crc;
		}
	}

	private int crc32(byte buffer[], int start, int count, int lastcrc) {
		int temp1, temp2;
		int i = start;

		crc = lastcrc;

		while (count-- != 0) {
			temp1 = crc >>> 8;
			temp2 = CRCTable[(crc ^ buffer[i++]) & 0xFF];
			crc = temp1 ^ temp2;
		}

		return crc;
	}

	public int crc32(int crc, byte buffer) {
		return crc32(new byte[] { buffer }, 0, 1, crc);
	}
}

Below is my complete code. Can anyone see what I'm doing wrong.

package org.apache.commons.compress.archivers.zip;

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

public class ZipCryptoInputStream extends InputStream {
	public class PKZIPCRC32 {
		private static final int	CRC32_POLYNOMIAL	= 0xdebb20e3;
		private int					crc					= 0xffffffff;
		private int					CRCTable[];

		public PKZIPCRC32() {
			buildCRCTable();
		}

		private void buildCRCTable() {
			int i, j;
			CRCTable = new int[256];
			for (i = 0; i <= 255; i++) {
				crc = i;
				for (j = 8; j > 0; j--)
					if ((crc & 1) == 1)
						crc = (crc >>> 1) ^ CRC32_POLYNOMIAL;
					else
						crc >>>= 1;
				CRCTable[i] = crc;
			}
		}

		private int crc32(byte buffer[], int start, int count, int lastcrc) {
			int temp1, temp2;
			int i = start;

			crc = lastcrc;

			while (count-- != 0) {
				temp1 = crc >>> 8;
				temp2 = CRCTable[(crc ^ buffer[i++]) & 0xFF];
				crc = temp1 ^ temp2;
			}

			return crc;
		}

		public int crc32(int crc, byte buffer) {
			return crc32(new byte[] { buffer }, 0, 1, crc);
		}
	}

	private static final long	ENCRYPTION_KEY_1	= 0x12345678;
	private static final long	ENCRYPTION_KEY_2	= 0x23456789;
	private static final long	ENCRYPTION_KEY_3	= 0x34567890;
	private InputStream			baseInputStream		= null;
	private final PKZIPCRC32	checksumEngine		= new PKZIPCRC32();
	private long[]				keys				= null;

	public ZipCryptoInputStream(ZipArchiveEntry zipEntry, InputStream inputStream, String passwd) throws Exception {
		baseInputStream = inputStream;
		// Decryption
		// ----------
		// PKZIP encrypts the compressed data stream. Encrypted files must
		// be decrypted before they can be extracted.
		//
		// Each encrypted file has an extra 12 bytes stored at the start of
		// the data area defining the encryption header for that file. The
		// encryption header is originally set to random values, and then
		// itself encrypted, using three, 32-bit keys. The key values are
		// initialized using the supplied encryption password. After each byte
		// is encrypted, the keys are then updated using pseudo-random number
		// generation techniques in combination with the same CRC-32 algorithm
		// used in PKZIP and described elsewhere in this document.
		//
		// The following is the basic steps required to decrypt a file:
		//
		// 1) Initialize the three 32-bit keys with the password.
		// 2) Read and decrypt the 12-byte encryption header, further
		// initializing the encryption keys.
		// 3) Read and decrypt the compressed data stream using the
		// encryption keys.

		// Step 1 - Initializing the encryption keys
		// -----------------------------------------
		//
		// Key(0) <- 305419896
		// Key(1) <- 591751049
		// Key(2) <- 878082192
		//
		// loop for i <- 0 to length(password)-1
		// update_keys(password(i))
		// end loop
		//
		// Where update_keys() is defined as:
		//
		// update_keys(char):
		// Key(0) <- crc32(key(0),char)
		// Key(1) <- Key(1) + (Key(0) & 000000ffH)
		// Key(1) <- Key(1) * 134775813 + 1
		// Key(2) <- crc32(key(2),key(1) >> 24)
		// end update_keys
		//
		// Where crc32(old_crc,char) is a routine that given a CRC value and a
		// character, returns an updated CRC value after applying the CRC-32
		// algorithm described elsewhere in this document.

		keys = new long[] { ENCRYPTION_KEY_1, ENCRYPTION_KEY_2, ENCRYPTION_KEY_3 };
		for (int i = 0; i < passwd.length(); ++i) {
			update_keys((byte) passwd.charAt(i));
		}

		// Step 2 - Decrypting the encryption header
		// -----------------------------------------
		//
		// The purpose of this step is to further initialize the encryption
		// keys, based on random data, to render a plaintext attack on the
		// data ineffective.
		//
		// Read the 12-byte encryption header into Buffer, in locations
		// Buffer(0) thru Buffer(11).
		//
		// loop for i <- 0 to 11
		// C <- buffer(i) ^ decrypt_byte()
		// update_keys(C)
		// buffer(i) <- C
		// end loop
		//
		// Where decrypt_byte() is defined as:
		//
		// unsigned char decrypt_byte()
		// local unsigned short temp
		// temp <- Key(2) | 2
		// decrypt_byte <- (temp * (temp ^ 1)) >> 8
		// end decrypt_byte
		//
		// 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.
		byte[] encryptionHeader = new byte[12];
		baseInputStream.read(encryptionHeader);
		for (int i = 0; i < encryptionHeader.length; i++) {
			encryptionHeader[i] ^= decrypt_byte();
			update_keys(encryptionHeader[i]);
		}
	}

	protected byte decrypt_byte() {
		byte temp = (byte) (keys[2] | 2);
		return (byte) ((temp * (temp ^ 1)) >> 8);
	}

	@Override
	public int read() throws IOException {
		//
		// Step 3 - Decrypting the compressed data stream
		// ----------------------------------------------
		//
		// The compressed data stream can be decrypted as follows:
		//
		// loop until done
		// read a character into C
		// Temp <- C ^ decrypt_byte()
		// update_keys(temp)
		// output Temp
		// end loop
		int read = baseInputStream.read();
		read ^= decrypt_byte();
		update_keys((byte) read);
		return read;
	}

	private final void update_keys(byte ch) {
		keys[0] = checksumEngine.crc32((int) keys[0], ch);
		keys[1] = keys[1] + (byte) keys[0];
		keys[1] = keys[1] * 134775813 + 1;
		keys[2] = checksumEngine.crc32((int) keys[2], (byte) (keys[1] >> 24));
	}

}

© Stack Overflow or respective owner

Related posts about java

Related posts about encryption