1 package java.util.zip;
   2 
   3 import java.util.Random;
   4 
   5 
   6 /**
   7  * This class implements a Traditional Zip Encryption / Decryption
   8  * engine according to <a href="https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT">the ZIP file format specification</a> to encrypt
   9  * / decrypt a data after it has been compressed.
  10  *
  11  * @since 1.9
  12  */
  13 public class TraditionalZipCryption implements ZipCryption {
  14 
  15     private static long[] crc32Table;
  16 
  17     private long[] keys;
  18 
  19     private String password;
  20 
  21     /**
  22      * Encryption header size
  23      */
  24     public static int ENCRYPTION_HEADER_SIZE = 12;
  25 
  26     private static long[] INITIAL_KEY = {305419896L, 591751049L, 878082192L};
  27 
  28     private static int CRC_TABLE_SIZE = 256;
  29 
  30     static {
  31         crc32Table = new long[CRC_TABLE_SIZE];
  32 
  33         /*
  34          * Calculate CRC-32 table
  35          *   make_crc_table()
  36          *     https://tools.ietf.org/html/rfc1952#section-8
  37          */
  38         for (int n = 0; n < CRC_TABLE_SIZE; n++) {
  39             long c = n;
  40 
  41             for (int k = 0; k < 8; k++) {
  42                 c = ((c & 1) == 1) ? 0xedb88320L ^ (c >>> 1) : (c >>> 1);
  43                 c &= 0xffffffffL;
  44             }
  45 
  46             crc32Table[n] = c;
  47         }
  48 
  49     }
  50 
  51     /**
  52      * Constructor of TraditionalZipCryption.
  53      * @param password ZIP password
  54      */
  55     public TraditionalZipCryption(String password) {
  56         this.password = password;
  57         this.keys = new long[ENCRYPTION_HEADER_SIZE];
  58         reset();
  59     }
  60 
  61     /**
  62      * update_crc()
  63      *   https://tools.ietf.org/html/rfc1952#section-8
  64      */
  65     private long crc32(long crc, int buf) {
  66         return
  67             (crc32Table[(int)(crc ^ buf) & 0xff] ^ (crc >>> 8)) & 0xffffffffL;
  68     }
  69 
  70     /**
  71      * update_keys()
  72      *   6.1.5 Initializing the encryption keys
  73      *     https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
  74      */
  75     private void updateKeys(int c){
  76         keys[0] = crc32(keys[0], c);
  77         keys[1] += keys[0] & 0xffL;
  78         keys[1] = ((keys[1] * 134775813L) + 1L) & 0xffffffffL;
  79         keys[2] = crc32(keys[2], (int)(keys[1] >>> 24));
  80     }
  81 
  82     /**
  83      * decrypt_byte()
  84      *   6.1.6 Decrypting the encryption header
  85      *     https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
  86      *
  87      * @return Decrypt byte
  88      */
  89     private int decryptByte() {
  90         int temp = (int)(keys[2] & 0xffffL) | 2;
  91         return ((temp * (temp ^ 1)) >>> 8) & 0xff;
  92     }
  93 
  94     private int encode(int unsignedByteData) {
  95         int decByte = decryptByte();
  96         updateKeys(unsignedByteData);
  97         return decByte ^ unsignedByteData;
  98     }
  99 
 100     private int decode(int unsignedByteData) {
 101         int decByte = (decryptByte() ^ unsignedByteData) & 0xff;
 102         updateKeys(decByte);
 103         return decByte;
 104     }
 105 
 106     /**
 107      * Calculate encription header
 108      *
 109      * @param e ZIP entry
 110      * @return ZIP encryption header
 111      */
 112     @Override
 113     public byte[] getEncryptionHeader(ZipEntry e) {
 114         /*
 115          * 6.1.6 Decrypting the encryption header
 116          *
 117          * After the header is decrypted,  the last 1 or 2 bytes in Buffer
 118          * should be the high-order word/byte of the CRC for the file being
 119          * decrypted, stored in Intel low-byte/high-byte order.  Versions of
 120          * PKZIP prior to 2.0 used a 2 byte CRC check; a 1 byte CRC check is
 121          * used on versions after 2.0.  This can be used to test if the password
 122          * supplied is correct or not.
 123          */
 124         Random rand = new Random();
 125         byte[] encryptionHeader = new byte[ENCRYPTION_HEADER_SIZE];
 126         rand.nextBytes(encryptionHeader);
 127 
 128         /* This code comes from testkey() at crypt.c in unzip 6.0 */
 129         encryptionHeader[ENCRYPTION_HEADER_SIZE - 1] =
 130                            (e.crc == -1) ? (byte)((e.xdostime >>> 8) & 0xffL)
 131                                          : (byte)((e.crc >>> 24) & 0xffL);
 132         encryptBytes(encryptionHeader);
 133         return encryptionHeader;
 134     }
 135 
 136     /**
 137      * {@inheritDoc}
 138      */
 139     @Override
 140     public byte[] encryptBytes(byte[] data, int offset, int length) {
 141 
 142         for (int idx = offset; idx < length; idx++) {
 143             data[idx] =  (byte)(encode(Byte.toUnsignedInt(data[idx])) & 0xff);
 144         }
 145 
 146         return data;
 147     }
 148 
 149     /**
 150      * {@inheritDoc}
 151      */
 152     @Override
 153     public byte[] encryptBytes(byte[] data) {
 154         return encryptBytes(data, 0, data.length);
 155     }
 156 
 157     /**
 158      * {@inheritDoc}
 159      */
 160     @Override
 161     public void reset() {
 162         keys[0] = INITIAL_KEY[0];
 163         keys[1] = INITIAL_KEY[1];
 164         keys[2] = INITIAL_KEY[2];
 165 
 166         for (byte b : password.getBytes()) {
 167             updateKeys(Byte.toUnsignedInt(b));
 168         }
 169 
 170     }
 171 
 172     /**
 173      * {@inheritDoc}
 174      */
 175     @Override
 176     public int getEncryptionHeaderSize() {
 177         return ENCRYPTION_HEADER_SIZE;
 178     }
 179 
 180     /**
 181      * {@inheritDoc}
 182      */
 183     @Override
 184     public boolean isValid(ZipEntry e, byte[] encryptionHeader) {
 185         /* This code comes from testkey() at crypt.c in unzip 6.0 */
 186         byte checkDigit = encryptionHeader[ENCRYPTION_HEADER_SIZE - 1];
 187 
 188         return (e.flag & 8) == 8
 189                             ? (checkDigit == (byte)((e.xdostime >>> 8) & 0xffL))
 190                             : (checkDigit == (byte)((e.crc >>> 24) & 0xffL));
 191     }
 192 
 193     /**
 194      * {@inheritDoc}
 195      */
 196     @Override
 197     public byte[] decryptBytes(byte[] data, int offset, int length) {
 198 
 199         for (int idx = offset; idx < length; idx++) {
 200             data[idx] =  (byte)(decode(Byte.toUnsignedInt(data[idx])) & 0xff);
 201         }
 202 
 203         return data;
 204     }
 205 
 206     /**
 207      * {@inheritDoc}
 208      */
 209     @Override
 210     public byte[] decryptBytes(byte[] data) {
 211         return decryptBytes(data, 0, data.length);
 212     }
 213 
 214 }