views:

451

answers:

2

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

}
+1  A: 

Why do you have to write your own? You can find several versions on line. I use this one,

http://csfviewer.googlecode.com/svn/src/csf/ZipCrypto.java

ZZ Coder
I searched for quite a while for zipcrypto and zip 2.0 encyrption and didn't really find anything, certainly didn't find the one you're linking to.I was trying to understand it rather than blindly copying code but now I'll use this to see where I'm going wrong.Cannot use existing code from someone else since I'm trying to do this for work it has to be apache style licensed code so even if I wanted to cannot just copy.
gomesla
Maybe to learn how it's work, or making a better API
Nettogrof
This one has GNU LGPL license so you can almost use it anywhere. Even if you want write your own, learning from working code can save you time.
ZZ Coder
Two big issues with what I was trying to do. 1. The magic number referenced in the PKWare documentation apparently has nothing to do with the way the CRC table is built. 2. Keep forgetting java uses signed numbers so have to account for that by moving up one level to larger storage so for example if you have unsigned int then you need to go to long makes coding easier. Also then have to do bit operations to get correct values.
gomesla
The code as is didn't work for me so most likely it was something I was doing wrong on my part. I realise it's re-inventing the wheel but at least it's a learning experience. Now just got to figure out if I can use the inbuilt java.util.zip.CRC32 to get what I need done instead of what I have.
gomesla
@gomesla: if you only use bitwise operations, you don't have to use unsigned integer types. The 8-bit numbers 255 and -1 have identical behavior as far as bitwise operations go. It's only when you want to display them or use addition/multiplication/division/sign extension that signed/unsigned math makes any difference.
Jason S
A: 

My final solution. Thanks to zzcoder

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

import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipException;

import org.apache.commons.io.EndianUtils;

public class ZipCryptoInputStream extends InputStream {

 private static final long[] CRC32_TABLE_PRECALCULATED = { 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL, 0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L, 0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L, 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L, 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL, 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L, 0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL, 0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L, 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L, 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL, 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L, 0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L, 0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L, 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL, 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L, 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL, 0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL, 0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L,
   0xE8B8D433L, 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L, 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L, 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL, 0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L, 0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL, 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L, 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L, 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL, 0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L, 0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L, 0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L, 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL, 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L, 0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL, 0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL, 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L, 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L, 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L, 0xF00F9344L, 0x8708A3D2L,
   0x1E01F268L, 0x6906C2FEL, 0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L, 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL, 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L, 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L, 0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL, 0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L, 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L, 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L, 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL, 0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L, 0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL, 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL, 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L, 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L, 0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L, 0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL, 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L, 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL, 0x8F659EFFL,
   0xF862AE69L, 0x616BFFD3L, 0x166CCF45L, 0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L, 0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL, 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L, 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L, 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L, 0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL, 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L, 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL };

 /*
  * Uses irreducible polynomial: 1 + x + x^2 + x^4 + x^5 + x^7 + x^8 + x^10 + x^11 + x^12 + x^16 + x^22 + x^23 + x^26
  * 
  * 0000 0100 1100 0001 0001 1101 1011 0111 0 4 C 1 1 D B 7
  * 
  * The reverse of this polynomial is
  * 
  * 0 2 3 8 8 B D E
  */

 private static final int CRC32_POLYNOMIAL   = 0xEDB88320;
 private static long[]  crc32Table     = CRC32_TABLE_PRECALCULATED;

 // This is just here to show how we get the table if it wasn't pre-calculated
 static {
  if (false) {
   int i, j;
   crc32Table = new long[256];
   for (i = 0; i <= 255; i++) {
    int crc = i;
    for (j = 8; j > 0; j--) {
     if ((crc & 1) == 1) {
      crc = (crc >>> 1) ^ CRC32_POLYNOMIAL;
     } else {
      crc >>>= 1;
     }
    }
    crc32Table[i] = Long.rotateLeft(crc, 32) >>> 32;
   }
  }
 }

 public static long crc32(long oldCrc, int character) {
  return crc32Table[(int) (oldCrc ^ character) & 0x000000ff] ^ (oldCrc >> 8);
 }

 // public static void main(String[] args) {
 // for (int i = 0; i < CRC_TABLE_PRECALCULATED.length; i++) {
 // System.out.println(Long.toHexString(CRC_TABLE_PRECALCULATED[i]) + "=" + Long.toHexString(crcTable[i]));
 // }
 // }

 private InputStream baseInputStream = null;

 private long[]  keys   = null;

 public ZipCryptoInputStream(ZipArchiveEntry zipEntry, InputStream inputStream, String passwd) throws Exception {
  // 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.

  baseInputStream = inputStream;

  // Step 1 - Initializing the encryption keys
  // -----------------------------------------
  //   
  // Key(0) <- 305419896
  // Key(1) <- 591751049
  // Key(2) <- 878082192

  keys = new long[] { 0x12345678l, 0x23456789l, 0x34567890l };

  // 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.
  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
  //   

  final byte[] encryptionHeader = new byte[12];
  for (int i = 0; i < 12; i++) {
   encryptionHeader[i] = (byte) read();
  }

  // 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[] passwordCheck = new byte[] { encryptionHeader[11], 0, 0, 0, 0, 0, 0, 0 };
  long suppliedPasswordCheck = EndianUtils.readSwappedLong(passwordCheck, 0);
  long actualPasswordCheck = zipEntry.getCrc() & 0xff000000;
  actualPasswordCheck = actualPasswordCheck >> 24;
  if (actualPasswordCheck != suppliedPasswordCheck) {
   throw new ZipException("Invalid password specified");
  }
 }

 private short decrypt_byte() {
  int t = (int) ((keys[2] & 0xFFFF) | 2);
  return (short) ((t * (t ^ 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 c = baseInputStream.read();
  if (c != -1) {
   c = c ^ decrypt_byte();
   update_keys((byte) c);
   c = c & 0xffff;
  }
  return c;
 }

 private void update_keys(short byteValue) {
  keys[0] = crc32(keys[0], byteValue);
  keys[1] = keys[1] + (keys[0] & 0x000000ffl);
  keys[1] = (keys[1] * 134775813) + 1;
  keys[2] = crc32(keys[2], (byte) (keys[1] >> 24));
 }
}
gomesla