gomesla
gomesla

Reputation: 76

Implementation of ZipCrypto / Zip 2.0 encryption in java

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

}

Upvotes: 1

Views: 11567

Answers (1)

gomesla
gomesla

Reputation: 76

My final solution—thanks to ZZ Coder:

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

Upvotes: 1

Related Questions