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