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));
}
}