Uploaded image for project: 'Commons Compress'
  1. Commons Compress
  2. COMPRESS-88

Adding encryption support to ZipFIle

    XMLWordPrintableJSON

Details

    • Wish
    • Status: Open
    • Minor
    • Resolution: Unresolved
    • None
    • None
    • Archivers
    • All

    Description

      I've been working on trying to get the ZipFIle code working with Winzip AES 256 encryption and zipcrypto. I have code that works however there are some issue with it.
      1. I've basically taken the ZipFIle and extended it rather than adding to it
      2. I've useded the decrypt portion of code from Olaf Merkerts solution to the AES decryption
      3. It relies on other libraries like BouncyCastle, EndianUtils etc for now.

      I'd really like to contribute but I am unsure how to proceed as:
      1. Does everything have to be one's own work?
      2. Can it be allowed to have dependencies on other libraries?

      ---------------
      1. ExtendedZipFIle
      ---------------
      /*

      • Licensed to the Apache Software Foundation (ASF) under one or more
      • contributor license agreements. See the NOTICE file distributed with
      • this work for additional information regarding copyright ownership.
      • The ASF licenses this file to You under the Apache License, Version 2.0
      • (the "License"); you may not use this file except in compliance with
      • the License. You may obtain a copy of the License at
        *
      • http://www.apache.org/licenses/LICENSE-2.0
        *
      • Unless required by applicable law or agreed to in writing, software
      • distributed under the License is distributed on an "AS IS" BASIS,
      • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      • See the License for the specific language governing permissions and
      • limitations under the License.
        *
        */
        package org.apache.commons.compress.archivers.zip;

      import java.io.File;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.RandomAccessFile;
      import java.util.Arrays;
      import java.util.Collections;
      import java.util.Enumeration;
      import java.util.HashMap;
      import java.util.Map;
      import java.util.zip.Inflater;
      import java.util.zip.InflaterInputStream;
      import java.util.zip.ZipException;

      import org.apache.commons.io.EndianUtils;
      import org.apache.commons.lang.StringUtils;

      /**

      • Replacement for <code>java.util.ZipFile</code>.
      • <p>
      • This class adds support for file name encodings other than UTF-8 (which is required to work on ZIP files created by native zip tools and is able to skip a preamble like the one found in self extracting archives. Furthermore it returns instances of <code>org.apache.commons.compress.archivers.zip.ZipArchiveEntry</code> instead of <code>java.util.zip.ZipEntry</code>.
      • </p>
      • <p>
      • It doesn't extend <code>java.util.zip.ZipFile</code> as it would have to reimplement all methods anyway. Like <code>java.util.ZipFile</code>, it uses RandomAccessFile under the covers and supports compressed and uncompressed entries.
      • </p>
      • <p>
      • The method signatures mimic the ones of <code>java.util.zip.ZipFile</code>, with a couple of exceptions:
      • <ul>
      • <li>There is no getName method.</li>
      • <li>entries has been renamed to getEntries.</li>
      • <li>getEntries and getEntry return <code>org.apache.commons.compress.archivers.zip.ZipArchiveEntry</code> instances.</li>
      • <li>close is allowed to throw IOException.</li>
      • </ul>
      • */
        /*********************

      • Built using http://www.pkware.com/documents/casestudies/APPNOTE.TXT http://cr.openjdk.java.net/~sherman/4681995/ZIP64TODO.txt
        */
        @SuppressWarnings("unchecked")
        public class ExtendedZipFile implements ExtendedZipFileInterface {
        /**
      • InputStream that delegates requests to the underlying RandomAccessFile, making sure that only bytes from a certain range can be read.
        */
        private class BoundedInputStream extends InputStream {
        private boolean addDummyByte = false;
        private long loc;
        private long remaining;

      BoundedInputStream(long start, long remaining)

      { this.remaining = remaining; loc = start; }

      /**

      • Inflater needs an extra dummy byte for nowrap - see Inflater's javadocs.
        */
        void addDummy() { addDummyByte = true; }

      public int read() throws IOException {
      if (remaining-- <= 0) {
      if (addDummyByte)

      { addDummyByte = false; return 0; }

      return -1;
      }
      synchronized (archive)

      { archive.seek(loc++); return archive.read(); }

      }

      public int read(byte[] b, int off, int len) throws IOException {
      if (remaining <= 0) {
      if (addDummyByte)

      { addDummyByte = false; b[off] = 0; return 1; }

      return -1;
      }

      if (len <= 0)

      { return 0; }

      if (len > remaining)

      { len = (int) remaining; }

      int ret = -1;
      synchronized (archive)

      { archive.seek(loc); ret = archive.read(b, off, len); }

      if (ret > 0)

      { loc += ret; remaining -= ret; }

      return ret;
      }
      }

      private static final class NameAndComment {
      private final byte[] comment;
      private final byte[] name;

      private NameAndComment(byte[] name, byte[] comment)

      { this.name = name; this.comment = comment; }

      }

      private static final class OffsetEntry

      { private long dataOffset = -1; private long headerOffset = -1; }

      private static final int BYTE_ARRAY_SIZE_DOUBLE_WORD = 8;
      private static final int BYTE_ARRAY_SIZE_SHORT = 2;
      private static final int BYTE_ARRAY_SIZE_WORD = 4;
      private static final int BYTE_SHIFT = 8;
      private static final int CFH_LEN =
      /* version made by */BYTE_ARRAY_SIZE_SHORT
      /* version needed to extract */+ BYTE_ARRAY_SIZE_SHORT
      /* general purpose bit flag */+ BYTE_ARRAY_SIZE_SHORT
      /* compression method */+ BYTE_ARRAY_SIZE_SHORT
      /* last mod file time */+ BYTE_ARRAY_SIZE_SHORT
      /* last mod file date */+ BYTE_ARRAY_SIZE_SHORT
      /* crc-32 */+ BYTE_ARRAY_SIZE_WORD
      /* compressed size */+ BYTE_ARRAY_SIZE_WORD
      /* uncompressed size */+ BYTE_ARRAY_SIZE_WORD
      /* filename length */+ BYTE_ARRAY_SIZE_SHORT
      /* extra field length */+ BYTE_ARRAY_SIZE_SHORT
      /* file comment length */+ BYTE_ARRAY_SIZE_SHORT
      /* disk number start */+ BYTE_ARRAY_SIZE_SHORT
      /* internal file attributes */+ BYTE_ARRAY_SIZE_SHORT
      /* external file attributes */+ BYTE_ARRAY_SIZE_WORD
      /* relative offset of local header */+ BYTE_ARRAY_SIZE_WORD;
      public static final int COMPRESSION_METHOD_ENCRYPTED_AES = 99;
      private static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
      private static final int HASH_SIZE = 509;

      public static final int HEADER_ID_AES_EXTRA_DATA = 0x9901;

      /**

      • Number of bytes in local file header up to the "length of filename" entry.
        */
        private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
        /* local file header signature */BYTE_ARRAY_SIZE_WORD
        /* version needed to extract */+ BYTE_ARRAY_SIZE_SHORT
        /* general purpose bit flag */+ BYTE_ARRAY_SIZE_SHORT
        /* compression method */+ BYTE_ARRAY_SIZE_SHORT
        /* last mod file time */+ BYTE_ARRAY_SIZE_SHORT
        /* last mod file date */+ BYTE_ARRAY_SIZE_SHORT
        /* crc-32 */+ BYTE_ARRAY_SIZE_WORD
        /* compressed size */+ BYTE_ARRAY_SIZE_WORD
        /* uncompressed size */+ BYTE_ARRAY_SIZE_WORD;

      private static final int MIN_EOCD_SIZE =
      /* end of central dir signature */BYTE_ARRAY_SIZE_WORD
      /* number of this disk */+ BYTE_ARRAY_SIZE_SHORT
      /* number of the disk with the start of the central directory */+ BYTE_ARRAY_SIZE_SHORT
      /* total number of entries in */
      /* the central dir on this disk */+ BYTE_ARRAY_SIZE_SHORT
      /* total number of entries in */
      /* the central dir */+ BYTE_ARRAY_SIZE_SHORT
      /* size of the central directory */+ BYTE_ARRAY_SIZE_WORD
      /* offset of start of central directory with respect to the starting disk number */+ BYTE_ARRAY_SIZE_WORD
      /* zipfile comment length */+ BYTE_ARRAY_SIZE_SHORT;

      private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
      /* maximum length of zipfile comment */+ 0xFFFF;

      private static final int MIN_ZIP64_EOCD_LOC_SIZE =
      /* zip64 end of central dir locator signature */BYTE_ARRAY_SIZE_WORD
      /* number of the disk with the start of the zip64 end of central directory */+ BYTE_ARRAY_SIZE_WORD
      /* central directory relatiove offset of the zip64 end of central directory record */+ BYTE_ARRAY_SIZE_DOUBLE_WORD
      /* total number of disks */+ BYTE_ARRAY_SIZE_WORD;
      private static final int NIBLET_MASK = 0x0f;
      private static final int POS_0 = 0;
      private static final int POS_1 = 1;
      private static final int POS_2 = 2;
      private static final int POS_3 = 3;

      private static final long ZIP64_DEFERRED_VALUE_STORAGE_SHORT = Long.decode("0xffff");

      private static final long ZIP64_DEFERRED_VALUE_STORAGE_WORD = Long.decode("0xffffffff");

      private static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L);

      private static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L);

      /**

      • close a zipfile quietly; throw no io fault, do nothing on a null parameter
      • @param zipfile
      • file to close, can be null
        */
        public static void closeQuietly(ZipFile zipfile) {
        if (zipfile != null)
        Unknown macro: { try { zipfile.close(); } catch (IOException e) { // ignore }
        }
        }

        /**
        * The actual data source.
        */
        private final RandomAccessFile archive;

        /**
        * The encoding to use for filenames and the file comment.
        *
        * <p>
        * For a list of possible values see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. Defaults to UTF-8.
        * </p>
        */
        private final String encoding;

        /**
        * Maps ZipArchiveEntrys to Longs, recording the offsets of the local file headers.
        */
        private final Map entries = new HashMap(HASH_SIZE);

        private boolean isZip64 = false;

        /**
        * Maps String to ZipArchiveEntrys, name -> actual entry.
        */
        private final Map nameMap = new HashMap(HASH_SIZE);

        private String password = null;

        private boolean printDebugStatements = false;

        /**
        * Whether to look for and use Unicode extra fields.
        */
        private final boolean useUnicodeExtraFields;

        /**
        * The zip encoding to use for filenames and the file comment.
        */
        private final ZipEncoding zipEncoding;

        /**
        * Opens the given file for reading, assuming "UTF8" for file names.
        *
        * @param f
        * the archive.
        *
        * @throws IOException
        * if an error occurs while reading the file.
        */
        public ExtendedZipFile(File f) throws IOException { this(f, ZipEncodingHelper.UTF8); }

        public ExtendedZipFile(File f, boolean printDebugStatements) throws IOException { this(f, ZipEncodingHelper.UTF8, true, printDebugStatements); }

        /**
        * Opens the given file for reading, assuming the specified encoding for file names and scanning for unicode extra fields.
        *
        * @param f
        * the archive.
        * @param encoding
        * the encoding to use for file names, use null for the platform's default encoding
        *
        * @throws IOException
        * if an error occurs while reading the file.
        */
        public ExtendedZipFile(File f, String encoding) throws IOException { this(f, encoding, true, false); }

        /**
        * Opens the given file for reading, assuming the specified encoding for file names.
        *
        * @param f
        * the archive.
        * @param encoding
        * the encoding to use for file names, use null for the platform's default encoding
        * @param useUnicodeExtraFields
        * whether to use InfoZIP Unicode Extra Fields (if present) to set the file names.
        *
        * @throws IOException
        * if an error occurs while reading the file.
        */
        public ExtendedZipFile(File f, String encoding, boolean useUnicodeExtraFields, boolean printDebugStatements) throws IOException {
        this.encoding = encoding;
        this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
        this.useUnicodeExtraFields = useUnicodeExtraFields;
        this.printDebugStatements = printDebugStatements;
        archive = new RandomAccessFile(f, "r");
        boolean success = false;
        try { Map entriesWithoutEFS = populateFromCentralDirectory(); resolveLocalFileHeaderData(entriesWithoutEFS); success = true; } finally {
        if (!success) {
        try { archive.close(); } catch (IOException e2) { // swallow, throw the original exception instead }
        }
        }
        }

        /**
        * Opens the given file for reading, assuming "UTF8".
        *
        * @param name
        * name of the archive.
        *
        * @throws IOException
        * if an error occurs while reading the file.
        */
        public ExtendedZipFile(String name) throws IOException { this(new File(name), ZipEncodingHelper.UTF8); }

        /**
        * Opens the given file for reading, assuming the specified encoding for file names, scanning unicode extra fields.
        *
        * @param name
        * name of the archive.
        * @param encoding
        * the encoding to use for file names, use null for the platform's default encoding
        *
        * @throws IOException
        * if an error occurs while reading the file.
        */
        public ExtendedZipFile(String name, String encoding) throws IOException { this(new File(name), encoding, true, false); }

        /**
        * Closes the archive.
        *
        * @throws IOException
        * if an error occurs closing the archive.
        */
        public void close() throws IOException { archive.close(); }

        /**
        * The encoding to use for filenames and the file comment.
        *
        * @return null if using the platform's default character encoding.
        */
        public String getEncoding() { return encoding; }

        /**
        * Returns all entries.
        *
        * @return all entries as {@link ZipArchiveEntry} instances
        */
        public Enumeration getEntries() { return Collections.enumeration(entries.keySet()); }

        /**
        * Returns a named entry - or <code>null</code> if no entry by that name exists.
        *
        * @param name
        * name of the entry.
        * @return the ZipArchiveEntry corresponding to the given name - or <code>null</code> if not present.
        */
        public ZipArchiveEntry getEntry(String name) { return (ZipArchiveEntry) nameMap.get(name); }

        /**
        * Returns an InputStream for reading the contents of the given entry.
        *
        * @param ze
        * the entry to get the stream for.
        * @return a stream to read the entry from.
        * @throws Exception
        */
        public InputStream getInputStream(ZipArchiveEntry ze) throws Exception {
        OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze);
        boolean encrypted = false;
        if (offsetEntry == null) { return null; }
        if (ze instanceof ExtendedZipArchiveEntry) {
        if (((ExtendedZipArchiveEntry) ze).isEncrypted()) {
        encrypted = true;
        if (ze.getMethod() == ExtendedZipArchiveEntry.COMPRESSION_METHOD_99_WINZIP_ENCRYPTED_AES) {

        } else { // throw new ZipException("ZipCrypto support not implemented"); }
        }
        }
        final long start = offsetEntry.dataOffset;
        final BoundedInputStream bis = new BoundedInputStream(start, ze.getCompressedSize());
        switch (ze.getMethod()) {
        case ZipArchiveEntry.STORED:
        if (encrypted) { return new ZipCryptoInputStream(ze, bis, getPassword()); } else { return bis; }
        case ZipArchiveEntry.DEFLATED:
        bis.addDummy();
        if (encrypted) { return new InflaterInputStream(new ZipCryptoInputStream(ze, bis, getPassword()), new Inflater(true)); } else { return new InflaterInputStream(bis, new Inflater(true)); }
        case ExtendedZipArchiveEntry.COMPRESSION_METHOD_99_WINZIP_ENCRYPTED_AES:
        return new WinzipAesDecryptedZipInputStream(ze, bis, getPassword());
        default:
        throw new ZipException("Found unsupported compression method " + ze.getMethod());
        }
        }

        public String getPassword() { return password; }

        private boolean isPrintDebugStatements() { return printDebugStatements; }

        /**
        * Reads the central directory of the given archive and populates the internal tables with ZipArchiveEntry instances.
        *
        * <p>
        * The ZipArchiveEntrys will know all data that can be obtained from the central directory alone, but not the data that requires the local file header or additional data to be read.
        * </p>
        *
        * @return a Map<ZipArchiveEntry, NameAndComment>> of zipentries that didn't have the language encoding flag set when read.
        */
        private Map populateFromCentralDirectory() throws IOException {
        final HashMap noEFS = new HashMap();
        positionAtCentralDirectory();

        final byte[] cfh = new byte[CFH_LEN];
        final byte[] signatureBytes = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(signatureBytes);
        long sig = ZipLong.getValue(signatureBytes);
        final long cfhSig = ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG);
        if (isPrintDebugStatements()) { System.out.println("Central directory Signature: " + Long.toHexString(sig) + " (" + Long.toHexString(cfhSig) + ")"); }
        if (sig != cfhSig && startsWithLocalFileHeader()) { throw new IOException("central directory is empty, can't expand" + " corrupt archive."); }
        // final StringBuilder sb = new StringBuilder();
        while (sig == cfhSig) {
        archive.readFully(cfh);
        int off = 0;
        final ExtendedZipArchiveEntry ze = new ExtendedZipArchiveEntry();

        int versionMadeBy = ZipShort.getValue(cfh, off);
        off += BYTE_ARRAY_SIZE_SHORT;
        ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK);

        off += BYTE_ARRAY_SIZE_SHORT; // skip version info

        final int generalPurposeFlag = ZipShort.getValue(cfh, off);
        final boolean hasEFS = (generalPurposeFlag & ZipArchiveOutputStream.EFS_FLAG) != 0;
        final ZipEncoding entryEncoding = hasEFS ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;

        ze.setEncrypted((generalPurposeFlag & ExtendedZipArchiveEntry.GENERAL_PURPOSE_BIT_FLAG_0_ENCRYPTED) != 0);

        off += BYTE_ARRAY_SIZE_SHORT;

        ze.setMethod(ZipShort.getValue(cfh, off));
        off += BYTE_ARRAY_SIZE_SHORT;

        // FIXME this is actually not very cpu cycles friendly as we are converting from
        // dos to java while the underlying Sun implementation will convert
        // from java to dos time for internal storage...
        long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfh, off));
        ze.setTime(time);
        off += BYTE_ARRAY_SIZE_WORD;

        ze.setCrc(ZipLong.getValue(cfh, off));
        off += BYTE_ARRAY_SIZE_WORD;

        ze.setCompressedSize(ZipLong.getValue(cfh, off));
        off += BYTE_ARRAY_SIZE_WORD;

        ze.setSize(ZipLong.getValue(cfh, off));
        off += BYTE_ARRAY_SIZE_WORD;

        int fileNameLen = ZipShort.getValue(cfh, off);
        off += BYTE_ARRAY_SIZE_SHORT;

        int extraLen = ZipShort.getValue(cfh, off);
        off += BYTE_ARRAY_SIZE_SHORT;

        int commentLen = ZipShort.getValue(cfh, off);
        off += BYTE_ARRAY_SIZE_SHORT;

        off += BYTE_ARRAY_SIZE_SHORT; // disk number

        ze.setInternalAttributes(ZipShort.getValue(cfh, off));
        off += BYTE_ARRAY_SIZE_SHORT;

        ze.setExternalAttributes(ZipLong.getValue(cfh, off));
        off += BYTE_ARRAY_SIZE_WORD;

        byte[] fileName = new byte[fileNameLen];
        archive.readFully(fileName);
        ze.setName(entryEncoding.decode(fileName));

        // LFH offset,
        OffsetEntry offset = new OffsetEntry();
        offset.headerOffset = ZipLong.getValue(cfh, off);
        // data offset will be filled later
        entries.put(ze, offset);

        nameMap.put(ze.getName(), ze);

        byte[] cdExtraData = new byte[extraLen];
        archive.readFully(cdExtraData);
        ze.setCentralDirectoryExtra(cdExtraData);

        byte[] comment = new byte[commentLen];
        archive.readFully(comment);
        ze.setComment(entryEncoding.decode(comment));

        archive.readFully(signatureBytes);
        sig = ZipLong.getValue(signatureBytes);

        if (isZip64) {
        // Zip64 Extended Information Extra Field (0x0001):
        //
        // The following is the layout of the zip64 extended
        // information "extra" block. If one of the size or
        // offset fields in the Local or Central directory
        // record is too small to hold the required data,
        // a Zip64 extended information record is created.
        // The order of the fields in the zip64 extended
        // information record is fixed, but the fields will
        // only appear if the corresponding Local or Central
        // directory record field is set to 0xFFFF or 0xFFFFFFFF.
        //
        // Note: all fields stored in Intel low-byte/high-byte order.
        //
        // Value Size Description
        // ----- ---- -----------
        // 0x0001 2 bytes Tag for this "extra" block type
        // Size 2 bytes Size of this "extra" block
        // Original Size 8 bytes Original uncompressed file size
        // Compressed Size 8 bytes Size of compressed data
        // Relative Header Offset 8 bytes Offset of local header record
        // Disk Start Number 4 bytes Number of the disk on which this file starts
        //
        // This entry in the Local header must include BOTH original
        // and compressed file size fields. If encrypting the
        // central directory and bit 13 of the general purpose bit
        // flag is set indicating masking, the value stored in the
        // Local Header for the original file size will be zero.
        final ZipExtraField zip64ExtraDataField = ze.getExtraField(new ZipShort(ExtendedZipArchiveEntry.EXTRA_DATA_HEADER_ID_0x0001_ZIP64_EXTRA_INFORMATION_FIELD));
        if (zip64ExtraDataField != null) {
        final byte[] zip64ExtraBlock = zip64ExtraDataField.getCentralDirectoryData();
        byte[] doubleWord = new byte[BYTE_ARRAY_SIZE_DOUBLE_WORD];
        int copyOffset = 0;
        if (ze.getSize() == ZIP64_DEFERRED_VALUE_STORAGE_WORD) { doubleWord = Arrays.copyOfRange(zip64ExtraBlock, copyOffset, BYTE_ARRAY_SIZE_DOUBLE_WORD); long actualSize = EndianUtils.readSwappedLong(doubleWord, 0); ze.setSize(actualSize); copyOffset += BYTE_ARRAY_SIZE_DOUBLE_WORD; }
        if (ze.getCompressedSize() == ZIP64_DEFERRED_VALUE_STORAGE_WORD) { doubleWord = Arrays.copyOfRange(zip64ExtraBlock, copyOffset, BYTE_ARRAY_SIZE_DOUBLE_WORD); ze.setCompressedSize(EndianUtils.readSwappedLong(doubleWord, 0)); copyOffset += BYTE_ARRAY_SIZE_DOUBLE_WORD; }
        if (offset.headerOffset == ZIP64_DEFERRED_VALUE_STORAGE_WORD) { doubleWord = Arrays.copyOfRange(zip64ExtraBlock, copyOffset, BYTE_ARRAY_SIZE_DOUBLE_WORD); offset.headerOffset = EndianUtils.readSwappedLong(doubleWord, 0); copyOffset += BYTE_ARRAY_SIZE_DOUBLE_WORD; }
        }
        }
        // if (isPrintDebugStatements()) { // sb.setLength(0); // sb.append(ze.getName() + ", " + ze.getSize() + " (" + Long.toHexString(ze.getSize()) + "), " + ze.getCompressedSize() + " (" + Long.toHexString(ze.getCompressedSize()) + "), " + offset.headerOffset + " (" + Long.toHexString(offset.headerOffset) + ")"); // System.out.println(sb.toString()); // }

        if (!hasEFS && useUnicodeExtraFields) { noEFS.put(ze, new NameAndComment(fileName, comment)); }
        }
        return noEFS;
        }

        /**
        * Searches for the "End of central dir record", parses it and positions the stream at the first central directory record.
        */
        private void positionAtCentralDirectory() throws IOException {
        final long startSearchOffset = archive.length() - MIN_EOCD_SIZE;
        final long stopSearchOffset = Math.max(0L, archive.length() - MAX_EOCD_SIZE);
        if (isPrintDebugStatements()) { System.out.println("Looking for End Of Central Directory Record ..."); }
        final long centralDirectoryLocatorOffset = positionAtSignature(startSearchOffset, stopSearchOffset, EOCD_SIG);
        if (centralDirectoryLocatorOffset <= 0) { throw new ZipException("archive is not a ZIP archive"); }
        if (isPrintDebugStatements()) { System.out.println("Found End Of Central Directory Record at " + centralDirectoryLocatorOffset); }
        archive.seek(centralDirectoryLocatorOffset);

        // End of central directory record:
        // end of central dir signature 4 bytes (0x06054b50)
        // number of this disk 2 bytes
        // number of the disk with the start of the central directory 2 bytes
        // total number of entries in the central directory on this disk 2 bytes
        // total number of entries in the central directory 2 bytes
        // size of the central directory 4 bytes
        // offset of start of central directory with respect to the
        // starting disk number 4 bytes
        // ZIP file comment length 2 bytes
        // ZIP file comment (variable size)

        // end of central dir signature - 4 bytes
        final byte[] eocdrSignature = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(eocdrSignature);
        // number of this disk - 2 bytes
        final byte[] eocdrNumberOfDisk = new byte[BYTE_ARRAY_SIZE_SHORT];
        archive.readFully(eocdrNumberOfDisk);
        // number of this disk - 2 bytes
        final byte[] eocdrNumberOfDiskWithStartOfCentralDirectory = new byte[BYTE_ARRAY_SIZE_SHORT];
        archive.readFully(eocdrNumberOfDiskWithStartOfCentralDirectory);
        // total number of entries in the central dir on this disk - 2 bytes
        final byte[] eocdrNumberOfEntriesInCentralDirOnThisDisk = new byte[BYTE_ARRAY_SIZE_SHORT];
        archive.readFully(eocdrNumberOfEntriesInCentralDirOnThisDisk);
        // total number of entries in the central dir - 2 bytes
        final byte[] eocdrNumberOfEntriesInCentralDir = new byte[BYTE_ARRAY_SIZE_SHORT];
        archive.readFully(eocdrNumberOfEntriesInCentralDir);
        // size of the central dir - 4 bytes
        final byte[] eocdrSizeOfCentralDir = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(eocdrSizeOfCentralDir);
        // offset of start of central directory with respect to the starting disk number - 4 bytes
        final byte[] cfdOffset = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(cfdOffset);
        // Zip file comment length - 2bytes
        final byte[] cfdCommentLength = new byte[BYTE_ARRAY_SIZE_SHORT];
        archive.readFully(cfdCommentLength);
        final byte[] cfdComment = new byte[ZipShort.getValue(cfdCommentLength)];
        archive.readFully(cfdComment);

        final StringBuilder sb = new StringBuilder();
        final int padLength = 100;
        if (isPrintDebugStatements()) { sb.append("End Of Central Directory Record:\n"); sb.append(StringUtils.rightPad("\tend of central dir signature", padLength)); sb.append("(4 bytes) : "); sb.append(Long.toHexString(ZipLong.getValue(eocdrSignature))); sb.append(" ("); sb.append(Long.toHexString(ZipLong.getValue(EOCD_SIG))); sb.append(")"); sb.append('\n'); sb.append(StringUtils.rightPad("\tnumber of this disk", padLength)); sb.append("(2 bytes) : "); sb.append(ZipShort.getValue(eocdrNumberOfDisk)); sb.append('\n'); sb.append(StringUtils.rightPad("\tnumber of the disk with the start of the central directory ", padLength)); sb.append("(2 bytes) : "); sb.append(ZipShort.getValue(eocdrNumberOfDiskWithStartOfCentralDirectory)); sb.append('\n'); sb.append(StringUtils.rightPad("\ttotal number of entries in the central directory on this disk", padLength)); sb.append("(2 bytes) : "); sb.append(ZipShort.getValue(eocdrNumberOfEntriesInCentralDirOnThisDisk)); sb.append('\n'); sb.append(StringUtils.rightPad("\ttotal number of entries in the central directory", padLength)); sb.append("(2 bytes) : "); sb.append(ZipShort.getValue(eocdrNumberOfEntriesInCentralDir)); sb.append('\n'); sb.append(StringUtils.rightPad("\tsize of the central directory", padLength)); sb.append("(4 bytes) : "); sb.append(ZipLong.getValue(eocdrSizeOfCentralDir)); sb.append('\n'); sb.append(StringUtils.rightPad("\toffset of start of central directory with respect to the starting disk number", padLength)); sb.append("(4 bytes) : "); sb.append(ZipLong.getValue(cfdOffset)); sb.append('\n'); sb.append(StringUtils.rightPad("\tzip file comment length", padLength)); sb.append("(2 bytes) : "); sb.append(ZipShort.getValue(cfdCommentLength)); sb.append('\n'); sb.append(StringUtils.rightPad("\tzip file comment", padLength)); sb.append("(variable): "); sb.append(new String(cfdComment)); sb.append('\n'); System.out.println(sb); }

        final ZipShort test1 = new ZipShort(eocdrNumberOfEntriesInCentralDirOnThisDisk);
        final ZipShort test2 = new ZipShort(eocdrNumberOfEntriesInCentralDir);
        final ZipShort test3 = new ZipShort(eocdrSizeOfCentralDir);
        final ZipLong test4 = new ZipLong(cfdOffset);

        // Whether or not we have zip64 end recorder & locator is decided by
        // (8)-(13) below, but we actually only look at the last 4
        // ----------------------------------------------------------------
        //
        // (8) number of this disk: (2 bytes) --->[do nothing]
        //
        // The number of this disk, which contains central
        // directory end record. If an archive is in ZIP64 format
        // and the value in this field is 0xFFFF, the size will
        // be in the corresponding 4 byte zip64 end of central
        // directory field.
        //
        //
        // (9) number of the disk with the start of the central
        // directory: (2 bytes) --->[do nothing]
        //
        // The number of the disk on which the central
        // directory starts. If an archive is in ZIP64 format
        // and the value in this field is 0xFFFF, the size will
        // be in the corresponding 4 byte zip64 end of central
        // directory field.
        //
        //
        // (10) total number of entries in the central dir on
        // this disk: (2 bytes)
        //
        // The number of central directory entries on this disk.
        // If an archive is in ZIP64 format and the value in
        // this field is 0xFFFF, the size will be in the
        // corresponding 8 byte zip64 end of central
        // directory field.
        //
        // (11) total number of entries in the central dir: (2 bytes)
        //
        // The total number of files in the .ZIP file. If an
        // archive is in ZIP64 format and the value in this field
        // is 0xFFFF, the size will be in the corresponding 8 byte
        // zip64 end of central directory field.
        //
        // (12) size of the central directory: (4 bytes)
        //
        // The size (in bytes) of the entire central directory.
        // If an archive is in ZIP64 format and the value in
        // this field is 0xFFFFFFFF, the size will be in the
        // corresponding 8 byte zip64 end of central
        // directory field.
        //
        // (13) offset of start of central directory with respect to
        // the starting disk number: (4 bytes)
        //
        // Offset of the start of the central directory on the
        // disk on which the central directory starts. If an
        // archive is in ZIP64 format and the value in this
        // field is 0xFFFFFFFF, the size will be in the
        // corresponding 8 byte zip64 end of central
        // directory field.
        if (test1.getValue() >= ZIP64_DEFERRED_VALUE_STORAGE_SHORT || test2.getValue() >= ZIP64_DEFERRED_VALUE_STORAGE_SHORT || test3.getValue() >= ZIP64_DEFERRED_VALUE_STORAGE_SHORT || test4.getValue() == ZIP64_DEFERRED_VALUE_STORAGE_WORD) {
        {
        isZip64 = true;
        if (isPrintDebugStatements()) { System.out.println("Looking for Zip64 end of central directory locator ..."); }
        final long zip64StartSearchOffset = archive.length() - MIN_EOCD_SIZE;
        // Have to account for space used by eocd as well
        final long zip64StopSearchOffset = Math.max(0L, archive.length() - MAX_EOCD_SIZE - MIN_ZIP64_EOCD_LOC_SIZE);
        final long zip64CentralDirectoryLocatorOffset = positionAtSignature(zip64StartSearchOffset, zip64StopSearchOffset, ZIP64_EOCD_LOC_SIG);
        if (zip64CentralDirectoryLocatorOffset <= 0) { throw new ZipException("Could not find Zip64 end of central directory locator"); }
        if (isPrintDebugStatements()) { System.out.println("Found Zip64 end of central directory locator at " + zip64CentralDirectoryLocatorOffset); }
        archive.seek(zip64CentralDirectoryLocatorOffset);
        // Zip64 end of central directory locator:
        // zip64 end of central dir locator signature 4 bytes (0x07064b50)
        // number of the disk with the start of the zip64 end of central directory 4 bytes
        // relative offset of the zip64 end of central directory record 8 bytes
        // total number of disks 4 bytes

        // end of central dir locator signature - 4 bytes
        final byte[] zip64EocdLocSignature = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(zip64EocdLocSignature);

        // number of the disk with the start of the zip64 end of central directory - 4 bytes
        final byte[] zip64NumberOfDiskWithStartOfCentralDirectory = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(zip64NumberOfDiskWithStartOfCentralDirectory);

        // relative offset of the zip64 end of central directory record - 8 bytes
        final byte[] zip64RelativeOffsetOfCentralDirectoryRecord = new byte[BYTE_ARRAY_SIZE_DOUBLE_WORD];
        archive.readFully(zip64RelativeOffsetOfCentralDirectoryRecord);
        long zip64EndOfCentralDirectoryOffset = EndianUtils.readSwappedLong(zip64RelativeOffsetOfCentralDirectoryRecord, 0);

        // total number of disks - 4 bytes
        final byte[] zip64TotalNumberOfDisks = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(zip64TotalNumberOfDisks);

        if (isPrintDebugStatements()) { sb.setLength(0); sb.append("Zip64 End Of Central Directory Locator:\n"); sb.append(StringUtils.rightPad("\tzip64 end of central dir locator signature", padLength)); sb.append("(4 bytes) : "); sb.append(Long.toHexString(ZipLong.getValue(zip64EocdLocSignature))); sb.append(" ("); sb.append(Long.toHexString(ZipLong.getValue(ZIP64_EOCD_LOC_SIG))); sb.append(")"); sb.append('\n'); sb.append(StringUtils.rightPad("\tnumber of the disk with the start of the zip64 end of central directory", padLength)); sb.append("(4 bytes) : "); sb.append(ZipLong.getValue(zip64NumberOfDiskWithStartOfCentralDirectory)); sb.append('\n'); sb.append(StringUtils.rightPad("\trelative offset of the zip64 end of central directory record", padLength)); sb.append("(8 bytes) : "); sb.append(zip64EndOfCentralDirectoryOffset); sb.append('\n'); sb.append(StringUtils.rightPad("\ttotal number of disks", padLength)); sb.append("(4 bytes) : "); sb.append(ZipLong.getValue(zip64TotalNumberOfDisks)); sb.append('\n'); System.out.println(sb.toString()); }
        if (isPrintDebugStatements()) { System.out.println("Moving to Zip64 end of central directory locator at " + zip64EndOfCentralDirectoryOffset); }
        archive.seek(zip64EndOfCentralDirectoryOffset);
        }
        {

        // Zip64 end of central directory record:
        // zip64 end of central dir signature 4 bytes (0x06064b50)
        // size of zip64 end of central directory record 8 bytes
        // version made by 2 bytes
        // version needed to extract 2 bytes
        // number of this disk 4 bytes
        // number of the disk with the start of the central directory 4 bytes
        // total number of entries in the central directory on this disk 8 bytes
        // total number of entries in the central directory 8 bytes
        // size of the central directory 8 bytes
        // offset of start of centraldirectory with respect to the starting disk number 8 bytes
        // zip64 extensible data sector (variable size)
        //
        // The value stored into the "size of zip64 end of central
        // directory record" should be the size of the remaining
        // record and should not include the leading 12 bytes.
        //
        // Size = SizeOfFixedFields + SizeOfVariableData - 12.

        // zip64 end of central dir signature - 4 bytes
        final byte[] zip64EocdSignature = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(zip64EocdSignature);

        // size of zip64 end of central directory record - 8 bytes
        final byte[] zip64EocdSize = new byte[BYTE_ARRAY_SIZE_DOUBLE_WORD];
        archive.readFully(zip64EocdSize);

        // version made by - 2 bytes
        final byte[] zip64VersionMadeBy = new byte[BYTE_ARRAY_SIZE_SHORT];
        archive.readFully(zip64VersionMadeBy);

        // version needed to extract - 2 bytes
        final byte[] zip64VersionNeededToExtract = new byte[BYTE_ARRAY_SIZE_SHORT];
        archive.readFully(zip64VersionNeededToExtract);

        // number of this disk - 4 bytes
        final byte[] zip64NumberOfThisDisk = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(zip64NumberOfThisDisk);

        // number of the disk with the start of the central directory - 4 bytes
        final byte[] zip64NumberOfDiskWithStartOfCentralDirectory = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(zip64NumberOfDiskWithStartOfCentralDirectory);

        // total number of entries in the central directory on this disk - 8 bytes
        final byte[] zip64TotalNumberOfEntriesOnThisDisk = new byte[BYTE_ARRAY_SIZE_DOUBLE_WORD];
        archive.readFully(zip64TotalNumberOfEntriesOnThisDisk);

        // total number of entries in the central directory - 8 bytes
        final byte[] zip64TotalNumberOfEntries = new byte[BYTE_ARRAY_SIZE_DOUBLE_WORD];
        archive.readFully(zip64TotalNumberOfEntries);

        // size of the central directory - 8 bytes
        final byte[] zip64SizeOfCentralDirectory = new byte[BYTE_ARRAY_SIZE_DOUBLE_WORD];
        archive.readFully(zip64SizeOfCentralDirectory);

        // offset of start of centraldirectory with respect to the starting disk number - 8 bytes
        final byte[] zip64CentralDirectoryOffset = new byte[BYTE_ARRAY_SIZE_DOUBLE_WORD];
        archive.readFully(zip64CentralDirectoryOffset);

        long zip64CentralDirOffset = EndianUtils.readSwappedLong(zip64CentralDirectoryOffset, 0);

        if (isPrintDebugStatements()) { sb.setLength(0); sb.append("Zip64 End Of Central Directory Record:\n"); sb.append(StringUtils.rightPad("\tzip64 end of central dir signature", padLength)); sb.append("(4 bytes) : "); sb.append(Long.toHexString(ZipLong.getValue(zip64EocdSignature))); sb.append(" ("); sb.append(Long.toHexString(ZipLong.getValue(ZIP64_EOCD_SIG))); sb.append(")"); sb.append('\n'); sb.append(StringUtils.rightPad("\tsize of zip64 end of central directory record", padLength)); sb.append("(8 bytes) : "); sb.append(EndianUtils.readSwappedLong(zip64EocdSize, 0)); sb.append('\n'); sb.append(StringUtils.rightPad("\tversion made by", padLength)); sb.append("(2 bytes) : "); sb.append(ZipShort.getValue(zip64VersionMadeBy)); sb.append('\n'); sb.append(StringUtils.rightPad("\tversion needed to extract", padLength)); sb.append("(2 bytes) : "); sb.append(ZipShort.getValue(zip64VersionNeededToExtract)); sb.append('\n'); sb.append(StringUtils.rightPad("\tnumber of this disk", padLength)); sb.append("(4 bytes) : "); sb.append(ZipLong.getValue(zip64NumberOfThisDisk)); sb.append('\n'); sb.append(StringUtils.rightPad("\tnumber of the disk with the start of the central directory", padLength)); sb.append("(4 bytes) : "); sb.append(ZipLong.getValue(zip64NumberOfDiskWithStartOfCentralDirectory)); sb.append('\n'); sb.append(StringUtils.rightPad("\ttotal number of entries in the central directory on this disk", padLength)); sb.append("(8 bytes) : "); sb.append(EndianUtils.readSwappedLong(zip64TotalNumberOfEntriesOnThisDisk, 0)); sb.append('\n'); sb.append(StringUtils.rightPad("\ttotal number of entries in the central directory", padLength)); sb.append("(8 bytes) : "); sb.append(EndianUtils.readSwappedLong(zip64TotalNumberOfEntries, 0)); sb.append('\n'); sb.append(StringUtils.rightPad("\tsize of the central directory", padLength)); sb.append("(8 bytes) : "); sb.append(EndianUtils.readSwappedLong(zip64SizeOfCentralDirectory, 0)); sb.append('\n'); sb.append(StringUtils.rightPad("\toffset of start of centraldirectory with respect to the starting disk number", padLength)); sb.append("(8 bytes) : "); sb.append(zip64CentralDirOffset); sb.append('\n'); System.out.println(sb.toString()); }
        // Move to start of central dir
        archive.seek(zip64CentralDirOffset);
        }

        } else { // Move to start of central dir archive.seek(ZipLong.getValue(cfdOffset)); }
        }

        private long positionAtSignature(long startingOffset, long stopSearchingOffset, byte[] sig) throws IOException {
        long off = startingOffset;
        boolean found = false;
        long stopSearching = Math.max(0L, stopSearchingOffset);
        if (off >= 0) {
        archive.seek;
        int curr = archive.read();
        while (off >= stopSearching && curr != -1) {
        if (curr == sig[POS_0]) {
        curr = archive.read();
        if (curr == sig[POS_1]) {
        curr = archive.read();
        if (curr == sig[POS_2]) {
        curr = archive.read();
        if (curr == sig[POS_3]) { found = true; break; }
        }
        }
        }
        archive.seek(--off);
        curr = archive.read();
        }
        }
        return found ? off : -1;
        }

        /**
        * Walks through all recorded entries and adds the data available from the local file header.
        *
        * <p>
        * Also records the offsets for the data to read from the entries.
        * </p>
        */
        private void resolveLocalFileHeaderData(Map entriesWithoutEFS) throws IOException {
        Enumeration e = getEntries();
        while (e.hasMoreElements()) {
        ZipArchiveEntry ze = (ZipArchiveEntry) e.nextElement();
        OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze);
        long offset = offsetEntry.headerOffset;
        archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
        byte[] b = new byte[BYTE_ARRAY_SIZE_SHORT];
        archive.readFully(b);
        int fileNameLen = ZipShort.getValue(b);
        archive.readFully(b);
        int extraFieldLen = ZipShort.getValue(b);
        int lenToSkip = fileNameLen;
        while (lenToSkip > 0) {
        int skipped = archive.skipBytes(lenToSkip);
        if (skipped <= 0) { throw new RuntimeException("failed to skip file name in" + " local file header"); }
        lenToSkip -= skipped;
        }
        byte[] localExtraData = new byte[extraFieldLen];
        archive.readFully(localExtraData);
        ze.setExtra(localExtraData);
        /*
        * dataOffsets.put(ze, new Long(offset + LFH_OFFSET_FOR_FILENAME_LENGTH + SHORT + SHORT + fileNameLen + extraFieldLen));
        */
        offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH + BYTE_ARRAY_SIZE_SHORT + BYTE_ARRAY_SIZE_SHORT + fileNameLen + extraFieldLen;

        if (entriesWithoutEFS.containsKey(ze)) {
        String orig = ze.getName();
        NameAndComment nc = (NameAndComment) entriesWithoutEFS.get(ze);
        ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name, nc.comment);
        if (!orig.equals(ze.getName())) { nameMap.remove(orig); nameMap.put(ze.getName(), ze); }
        }
        }
        }

        public void setPassword(String password) { this.password = password; }

        /**
        * Checks whether the archive starts with a LFH. If it doesn't, it may be an empty archive.
        */
        private boolean startsWithLocalFileHeader() throws IOException {
        archive.seek(0);
        final byte[] start = new byte[BYTE_ARRAY_SIZE_WORD];
        archive.readFully(start);
        for (int i = 0; i < start.length; i++) {
        if (start[i] != ZipArchiveOutputStream.LFH_SIG[i]) { return false; }
        }
        return true;
        }
        }

        ---------------
        2. ExtendedZipArchiveEntry
        ---------------
        package org.apache.commons.compress.archivers.zip;

        public class ExtendedZipArchiveEntry extends ZipArchiveEntry {
        // compression method: (2 bytes)

        public static final int COMPRESSION_METHOD_00_STORED = 0; // 0 - The file is stored (no compression)
        public static final int COMPRESSION_METHOD_01_SHRUNK = 1; // 1 - The file is Shrunk
        public static final int COMPRESSION_METHOD_02_COMPRESSED_LV1 = 2; // 2 - The file is Reduced with compression factor 1
        public static final int COMPRESSION_METHOD_03_COMPRESSED_LV2 = 3; // 3 - The file is Reduced with compression factor 2
        public static final int COMPRESSION_METHOD_04_COMPRESSED_LV3 = 4; // 4 - The file is Reduced with compression factor 3
        public static final int COMPRESSION_METHOD_05_COMPRESSED_LV4 = 5; // 5 - The file is Reduced with compression factor 4
        public static final int COMPRESSION_METHOD_06_IMPLODED = 6; // 6 - The file is Imploded
        public static final int COMPRESSION_METHOD_07_RESERVED_TOKENIZING_COMPRESSIONALGORITHM = 7; // 7 - Reserved for Tokenizing compression algorithm
        public static final int COMPRESSION_METHOD_08_DEFLATED = 8; // 8 - The file is Deflated
        public static final int COMPRESSION_METHOD_09_DEFLATE64 = 9; // 9 - Enhanced Deflating using Deflate64(tm)
        public static final int COMPRESSION_METHOD_10_PKWARE_IMPLODING = 10; // 10 - PKWARE Data Compression Library Imploding (old IBM TERSE)
        public static final int COMPRESSION_METHOD_11_RESERVED_PKWARE = 11; // 11 - Reserved by PKWARE
        public static final int COMPRESSION_METHOD_12_BZIP2 = 12; // 12 - File is compressed using BZIP2 algorithm
        public static final int COMPRESSION_METHOD_13_RESERVED_PKWARE = 13; // 13 - Reserved by PKWARE
        public static final int COMPRESSION_METHOD_14_LZMA_EFS = 14; // 14 - LZMA (EFS)
        public static final int COMPRESSION_METHOD_15_RESERVED_PKWARE = 15; // 15 - Reserved by PKWARE
        public static final int COMPRESSION_METHOD_16_RESERVED_PKWARE = 16; // 16 - Reserved by PKWARE
        public static final int COMPRESSION_METHOD_17_RESERVED_PKWARE = 17; // 17 - Reserved by PKWARE
        public static final int COMPRESSION_METHOD_18_COMPRESSED_IBM_TERSE = 18; // 18 - File is compressed using IBM TERSE (new)
        public static final int COMPRESSION_METHOD_19_IBM_LZ77_Z = 19; // 19 - IBM LZ77 z Architecture (PFS)
        public static final int COMPRESSION_METHOD_97_WAVPACK_COMPRESSED = 97; // 97 - WavPack compressed data
        public static final int COMPRESSION_METHOD_98_PPMD_VER1_REV1 = 98; // 98 - PPMd version I, Rev 1
        public static final int COMPRESSION_METHOD_99_WINZIP_ENCRYPTED_AES = 99; // 99 - Winzip AES - http://www.winzip.com/aes_info.htm

        // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
        // The current Header ID mappings defined by PKWARE are:
        public static final int EXTRA_DATA_HEADER_ID_0x0001_ZIP64_EXTRA_INFORMATION_FIELD = 0x0001; // 0x0001 Zip64 extended information extra field
        public static final int EXTRA_DATA_HEADER_ID_0x0007_AV_INFO = 0x0007; // 0x0007 AV Info
        public static final int EXTRA_DATA_HEADER_ID_0x0008_RESERVED_EXTENDED_LANGUAGE_ENCODING_PFS = 0x0008; // 0x0008 Reserved for extended language encoding data (PFS)
        public static final int EXTRA_DATA_HEADER_ID_0x0009_OS2 = 0x0009; // 0x0009 OS/2
        public static final int EXTRA_DATA_HEADER_ID_0x000a_NTFS = 0x000a; // 0x000a NTFS
        public static final int EXTRA_DATA_HEADER_ID_0x000c_OPENVMS = 0x000c; // 0x000c OpenVMS
        public static final int EXTRA_DATA_HEADER_ID_0x000d_UNIX = 0x000d; // 0x000d UNIX
        public static final int EXTRA_DATA_HEADER_ID_0x000e_RESERVED_STREAM_FORK_DESCRIPTORS = 0x000e; // 0x000e Reserved for file stream and fork descriptors
        public static final int EXTRA_DATA_HEADER_ID_0x000f_PATCH_DESCRIPTOR = 0x000f; // 0x000f Patch Descriptor
        public static final int EXTRA_DATA_HEADER_ID_0x0014_PKCS7_STORE_FOR_X509_CERTIFICATES = 0x0014; // 0x0014 PKCS#7 Store for X.509 Certificates
        public static final int EXTRA_DATA_HEADER_ID_0x0015_X509_CERTIFICATE_ID_AND_SIGNATURE_FOR_INDIVIDUAL_FILE = 0x0015; // 0x0015 X.509 Certificate ID and Signature for individual file
        public static final int EXTRA_DATA_HEADER_ID_0x0016_X509_CERTIFICATE_ID_FOR_CENTRAL_DIRECTORY = 0x0016; // 0x0016 X.509 Certificate ID for Central Directory
        public static final int EXTRA_DATA_HEADER_ID_0x0017_STRONG_ENCRYPTION_HEADER = 0x0017; // 0x0017 Strong Encryption Header
        public static final int EXTRA_DATA_HEADER_ID_0x0018_RECORD_MANAGEMENT_CONTROLS = 0x0018; // 0x0018 Record Management Controls
        public static final int EXTRA_DATA_HEADER_ID_0x0019_PKCS7_ENCRYPTION_RECIPIENT_CERTIFICATE_LIST = 0x0019; // 0x0019 PKCS#7 Encryption Recipient Certificate List
        public static final int EXTRA_DATA_HEADER_ID_0x0065_IBM_S390_Z390_AS400_I400_ATTRIBUTES_UNCOMPRESSED = 0x0065; // 0x0065 IBM S/390 (Z390), AS/400 (I400) attributes - uncompressed
        public static final int EXTRA_DATA_HEADER_ID_0x0066_IBM_S390_Z390_AS400_I400_ATTRIBUTES_COMPRESSED = 0x0066; // 0x0066 Reserved for IBM S/390 (Z390), AS/400 (I400) attributes - compressed
        // Third party mappings commonly used are:
        public static final int EXTRA_DATA_HEADER_ID_0x07c8_MACINTOSH = 0x07c8; // 0x07c8 Macintosh
        public static final int EXTRA_DATA_HEADER_ID_0x2605_ZIPIT_MACINTOSH = 0x2605; // 0x2605 ZipIt Macintosh
        public static final int EXTRA_DATA_HEADER_ID_0x2705_ZIPIT_MACINTOSH = 0x2705; // 0x2705 ZipIt Macintosh 1.3.5+
        public static final int EXTRA_DATA_HEADER_ID_0x2805_ZIPIT_MACINTOSH = 0x2705; // 0x2805 ZipIt Macintosh 1.3.5+
        public static final int EXTRA_DATA_HEADER_ID_0x334d_INFOZIP_MACINTOSH = 0x334d; // 0x334d Info-ZIP Macintosh
        public static final int EXTRA_DATA_HEADER_ID_0x4341_ACORN_SPARKFS = 0x4341; // 0x4341 Acorn/SparkFS
        public static final int EXTRA_DATA_HEADER_ID_0x4453_WINNT_SECURITY_DESCRIPTOR_BINARY_ACL = 0x4453; // 0x4453 Windows NT security descriptor (binary ACL)
        public static final int EXTRA_DATA_HEADER_ID_0x4690_POSZIP_4690 = 0x4690; // 0x4690 POSZIP 4690 (reserved)
        public static final int EXTRA_DATA_HEADER_ID_0x4704_VM_CMS = 0x4704; // 0x4704 VM/CMS
        public static final int EXTRA_DATA_HEADER_ID_0x470f_MVS = 0x470f; // 0x470f MVS
        public static final int EXTRA_DATA_HEADER_ID_0x4b46_FWKCS_MD5 = 0x4b46; // 0x4b46 FWKCS MD5 (see below)
        public static final int EXTRA_DATA_HEADER_ID_0x4c41_OS2_TEXT_ACL = 0x4c41; // 0x4c41 OS/2 access control list (text ACL)
        public static final int EXTRA_DATA_HEADER_ID_0x4d49_INFO_ZIP_OPENVMS = 0x4d49; // 0x4d49 Info-ZIP OpenVMS
        public static final int EXTRA_DATA_HEADER_ID_0x4f4c_INFO_ZIP_OPENVMS = 0x4f4c; // 0x4f4c Xceed original location extra field
        public static final int EXTRA_DATA_HEADER_ID_0x5356_AOS_VS_ACL = 0x5356; // 0x5356 AOS/VS (ACL)
        public static final int EXTRA_DATA_HEADER_ID_0x5455_EXTENDED_TIMESTAMP = 0x5455; // 0x5455 extended timestamp
        public static final int EXTRA_DATA_HEADER_ID_0x554e_XCEED_UNICODE = 0x554e; // 0x554e Xceed unicode extra field
        public static final int EXTRA_DATA_HEADER_ID_0x5855_INFOZIP_UNIX = 0x5855; // 0x5855 Info-ZIP UNIX (original, also OS/2, NT, etc)
        public static final int EXTRA_DATA_HEADER_ID_0x6375_INFOZIP_UNICODE_COMMENT = 0x6375; // 0x6375 Info-ZIP Unicode Comment Extra Field
        public static final int EXTRA_DATA_HEADER_ID_0x6542_BEOS_BEBOX = 0x6542; // 0x6542 BeOS/BeBox
        public static final int EXTRA_DATA_HEADER_ID_0x7075_INFOZIP_UNICODE_PATH = 0x7075; // 0x7075 Info-ZIP Unicode Path Extra Field
        public static final int EXTRA_DATA_HEADER_ID_0x756e_ASI_UNIX = 0x756e; // 0x756e ASi UNIX
        public static final int EXTRA_DATA_HEADER_ID_0x7855_INFOZIP_UNIX = 0x7855; // 0x7855 Info-ZIP UNIX (new)
        public static final int EXTRA_DATA_HEADER_ID_0x9901_WINZIP_ENCRYPTED_AES = 0x9901; // Winzip AES - http://www.winzip.com/aes_info.htm#extra-data
        public static final int EXTRA_DATA_HEADER_ID_0xa220_MICROSOFT_OPEN_PACKAGING_GROWTH_HINT = 0xa220; // 0xa220 Microsoft Open Packaging Growth Hint
        public static final int EXTRA_DATA_HEADER_ID_0xfd4a_SMS_QDOS = 0xfd4a; // 0xfd4a SMS/QDOS

        public static final int GENERAL_PURPOSE_BIT_FLAG_0_ENCRYPTED = 1;

        // Have to do this to handle the extra compression methods like AES which ZipEntry will throw exception for
        private int compressionMethod = -1;
        private boolean encrypted = false;
        // Bypassing this check in java.util.zip.ZipEntry
        // if (size < 0 || size > 0xFFFFFFFFL) { // throw new IllegalArgumentException("invalid entry size"); // }
        private long size = 0l;

        @Override
        public int getMethod() { return compressionMethod; }

        public long getSize() { return size; }

        public boolean isEncrypted() { return encrypted; }

        public void setEncrypted(boolean encrypted) { this.encrypted = encrypted; }

        @Override
        public void setMethod(int method) {
        compressionMethod = method;
        if (method == COMPRESSION_METHOD_99_WINZIP_ENCRYPTED_AES) { // java's zip implementation doesn't like this compression method, silently handle it ourself } else { super.setMethod(method); }
        }

        public void setSize(long size) { this.size = size; }

        }

        ---------------
        3. ExtendedZipFIleInterface
        ---------------
        package org.apache.commons.compress.archivers.zip;

        import java.io.InputStream;
        import java.util.Enumeration;

        public interface ExtendedZipFileInterface {
        @SuppressWarnings("unchecked")
        public Enumeration getEntries();

        public ZipArchiveEntry getEntry(String name);

        public InputStream getInputStream(ZipArchiveEntry zipArchiveEntry) throws Exception;

        public void close() throws Exception;
        }

        ---------------
        4. ExtendedZipFIleTools
        ---------------
        package org.apache.commons.compress.archivers.zip;

        import java.io.File;
        import java.io.IOException;

        import org.apache.log4j.Logger;

        public class ExtendedZipFileTools {
        public static final Logger logger = Logger.getLogger(ExtendedZipFileTools.class);

        public static ExtendedZipFileInterface getExtendedZipFileInterface(File zipFile) throws IOException {
        // Try normal one which will try and populate central directory
        try { return new ExtendedZipFileAdapterForOriginalZipFile(zipFile); } catch (Exception e) { logger.warn("Caught exception " + e.getMessage() + "... fallback to ExtendedZipFile for " + zipFile.getPath()); // Now fall back to our new implementation return new ExtendedZipFile(zipFile); }
        }

        public static void closeQuietly(ExtendedZipFileInterface zipFile) {
        if (zipFile != null) {
        try { zipFile.close(); } catch (Exception e) { // ignore } }

        }
        }
        ---------------
        5. WinzipAesDecryptedZipInputStream
        ---------------
        package org.apache.commons.compress.archivers.zip;

      import java.io.ByteArrayInputStream;
      import java.io.ByteArrayOutputStream;
      import java.io.IOException;
      import java.io.InputStream;
      import java.util.zip.Inflater;
      import java.util.zip.InflaterInputStream;
      import java.util.zip.ZipException;

      import org.apache.commons.io.EndianUtils;
      import org.bouncycastle.crypto.CipherParameters;
      import org.bouncycastle.crypto.PBEParametersGenerator;
      import org.bouncycastle.crypto.engines.AESEngine;
      import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
      import org.bouncycastle.crypto.modes.SICBlockCipher;
      import org.bouncycastle.crypto.params.KeyParameter;
      import org.bouncycastle.crypto.params.ParametersWithIV;

      // Specifications from: http://www.winzip.com/aes_info.htm
      // Code borrowed from: http://code.google.com/p/winzipaes/ by <a href="mailto:olaf@merkert.de">Olaf Merkert</a>

      public class WinzipAesDecryptedZipInputStream extends InputStream {

      private class WrappedWinzipAesDecryptedInputStream extends InputStream {
      private static final int BUFFER_SIZE = 10 * 1024 * 1024;
      private final SICBlockCipher aesCipher = new SICBlockCipher(new AESEngine());
      private ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
      private final int blockSize = aesCipher.getBlockSize();
      private byte[] buffer = null;
      private long bytesLeftToRead = 0l;
      final byte[] decryptedIn = new byte[blockSize];
      private InputStream ecnryptedInputStream = null;
      private int nonce = 1;

      public WrappedWinzipAesDecryptedInputStream(final long bytesToRead, final InputStream encryptedStream)

      { this.bytesLeftToRead = bytesToRead; this.ecnryptedInputStream = encryptedStream; // incremented on each 16 byte block and used as encryption NONCE (ivBytes) nonce = 1; // Encrypted file data // Encryption is applied only to the content of files. It is performed after compression, and not to any other associated data. // The file data is encrypted byte-for-byte using the AES encryption algorithm operating in "CTR" mode, which means that the // lengths of the compressed data and the compressed, encrypted data are the same. // It is important for implementors to note that, although the data is encrypted byte-for-byte, it is presented to the encryption and // decryption functions in blocks. The block size used for encryption and decryption must be the same. To be compatible with the // encryption specification, this block size must be 16 bytes (although the last block may be smaller). // Implements the Segmented Integer Counter (SIC) mode on top of a simple block cipher. This mode is also known as CTR mode. // If bytes left to read is smaller than our minimum buffer size why use up space final int bufSize = bytesLeftToRead > BUFFER_SIZE ? BUFFER_SIZE : (int) bytesLeftToRead; buffer = new byte[bufSize]; }

      private void decipherNextBlock() throws IOException {
      if (bytesLeftToRead > 0) {
      final ByteArrayOutputStream baos = new ByteArrayOutputStream();
      final int len = (bytesLeftToRead > buffer.length) ? buffer.length : (int) bytesLeftToRead;
      final int read = ecnryptedInputStream.read(buffer, 0, len);
      int pos = 0;
      while (pos < buffer.length && pos < read) {
      final ParametersWithIV ivParams = new ParametersWithIV(aesCipherParameters, toByteArray(nonce++, 16));
      aesCipher.init(false, ivParams);
      final int remainingCount = read - pos;
      if (remainingCount >= blockSize)

      { aesCipher.processBlock(buffer, pos, decryptedIn, 0); System.arraycopy(decryptedIn, 0, buffer, pos, blockSize); }

      else

      { final byte[] extendedIn = new byte[blockSize]; System.arraycopy(buffer, pos, extendedIn, 0, remainingCount); aesCipher.processBlock(extendedIn, 0, decryptedIn, 0); System.arraycopy(decryptedIn, 0, buffer, pos, remainingCount); }

      pos += blockSize;
      }
      baos.write(buffer, 0, read);
      bais = new ByteArrayInputStream(baos.toByteArray());
      bytesLeftToRead -= len;
      }
      }

      @Override
      public int read() throws IOException {
      int byteRead = bais.read();
      // If we still have data and we're at the end of our buffer read next block
      if (bytesLeftToRead > 0 && byteRead == -1)

      { decipherNextBlock(); byteRead = bais.read(); }

      return byteRead;
      }

      }

      private static String byteArrayToHexString(byte[] theByteArray) {
      StringBuffer out = new StringBuffer();
      for (int i = 0; i < theByteArray.length; i++) {
      String s = Integer.toHexString(theByteArray[i] & 0xff);
      if (s.length() < 2)

      { out.append('0'); }

      out.append(s).append(' ');
      }
      return out.toString();
      }

      private static boolean isEqual(byte[] first, byte[] second) {
      boolean out = first != null && second != null && first.length == second.length;
      for (int i = 0; out && i < first.length; i++) {
      if (first[i] != second[i])

      { out = false; }

      }
      return out;
      }

      public static byte[] toByteArray(int in)

      { byte[] out = new byte[4]; out[0] = (byte) in; out[1] = (byte) (in >> 8); out[2] = (byte) (in >> 16); out[3] = (byte) (in >> 24); return out; }

      public static byte[] toByteArray(int in, int outSize) {
      byte[] out = new byte[outSize];
      byte[] intArray = toByteArray(in);
      for (int i = 0; i < intArray.length && i < outSize; i++)

      { out[i] = intArray[i]; }

      return out;
      }

      private CipherParameters aesCipherParameters = null;
      private byte aesEncryptionStrength = 0;
      private InputStream baseInputStream = null;
      private int compressionType = 0;
      private byte[] cryptoKeyBytes = null;
      private byte[] passwordBytes = null;
      private byte[] passwordVerificationBytes = null;
      private byte[] saltBytes = null;

      public WinzipAesDecryptedZipInputStream(ZipArchiveEntry zipEntry, InputStream is, String passwd) throws Exception

      { passwordBytes = passwd.getBytes(); initialize(zipEntry, is); setupInputStream(zipEntry, is); }

      private void initialize(ZipArchiveEntry zipEntry, InputStream is) throws IOException {
      // # The format of the data in the AES extra data field is as follows. See the notes below for additional information.
      // Offset Size(bytes) Content
      // 0 2 Extra field header ID (0x9901)
      // 2 2 Data size (currently 7, but subject to possible increase in the future)
      // 4 2 Integer version number specific to the zip vendor
      // 6 2 2-character vendor ID
      // 8 1 Integer mode value indicating AES encryption strength
      // 9 2 The actual compression method used to compress the file
      //
      // # Notes
      //
      // * Data size: this value is currently 7, but because it is possible that this specification will be modified in the future to store additional data in this extra field, vendors should not assume that it will always remain 7.
      // * Vendor ID: the vendor ID field should always be set to the two ASCII characters "AE".
      // * Vendor version: the vendor version for AE-1 is 0x0001. The vendor version for AE-2 is 0x0002.
      //
      // Zip utilities that support AE-2 must also be able to process files that are encrypted in AE-1 format. The handling of the CRC value is the only difference between the AE-1 and AE-2 formats.
      //
      // * Encryption strength: the mode values (encryption strength) for AE-1 and AE-2 are:
      // Value Strength
      // 0x01 128-bit encryption key
      // 0x02 192-bit encryption key
      // 0x03 256-bit encryption key
      //
      // The encryption specification supports only 128-, 192-, and 256-bit encryption keys. No other key lengths are permitted.
      //
      // (Note: the current version of WinZip does not support encrypting files using 192-bit keys. This specification, however, does provide for the use of 192-bit keys, and WinZip is able to decrypt such files.)
      // * Compression method: the compression method is the one that would otherwise have been stored in the local and central headers for the file. For example, if the file is imploded, this field will contain the compression code 6. This is needed because a compression method of 99 is used to indicate the presence of an AES-encrypted file (see above).
      //

      // Calculate encryption strength and compression type
      final ZipExtraField aesExtraDataField = zipEntry.getExtraField(new ZipShort(ExtendedZipArchiveEntry.EXTRA_DATA_HEADER_ID_0x9901_WINZIP_ENCRYPTED_AES));
      final byte[] extraData = aesExtraDataField.getLocalFileDataData();
      aesEncryptionStrength = extraData[4];
      // This will be 7 bytes long since the total size is 11 bytes - 2 byte header id - 2 byte data size
      final byte[] data = aesExtraDataField.getLocalFileDataData();
      // Compressing stored in last two bytes. See "* Compression method:" above
      final byte[] compression = new byte[2];
      compression[0] = data[5];
      compression[1] = data[6];
      compressionType = EndianUtils.readSwappedShort(compression, 0);

      // AES Encryption Strength
      // Value Strength
      // 0x01 128-bit encryption key
      // 0x02 192-bit encryption key
      // 0x03 256-bit encryption key

      // The size of the salt value depends on the length of the encryption key, as follows:
      // Key size Salt size
      // 128 bits 8 bytes
      // 192 bits 12 bytes
      // 256 bits 16 bytes

      int saltBytesLength = 8;
      int aesKeyBitLength = 128;
      if (aesEncryptionStrength == 2)

      { saltBytesLength = 12; aesKeyBitLength = 192; }

      if (aesEncryptionStrength == 3)

      { saltBytesLength = 16; aesKeyBitLength = 256; }

      // File format
      // Additional overhead data required for decryption is stored with the encrypted file itself (i.e., not in the headers). The actual format of the stored file is as follows; additional information about these fields is below. All fields are byte-aligned.
      // Size (bytes) - Content
      // -----------------------
      // n bytes - Salt value is variable depending on encryption strength
      // 2 bytes - Password verification value
      // m bytes - Encrypted file data where m = zipEntry.getCompressedSize() - authentication (10) - salt - verification (2)
      // 10 bytes - Authentication code

      // Read salt and password verification
      saltBytes = new byte[saltBytesLength];
      is.read(saltBytes, 0, saltBytesLength);
      passwordVerificationBytes = new byte[2];
      is.read(passwordVerificationBytes, 0, 2);

      // Key Generation
      // Key derivation, as used by AE-1 and AE-2 and as implemented in Dr. Gladman's library, is done according to the PBKDF2 algorithm,
      // which is described in the RFC2898 guidelines. An iteration count of 1000 is used. An appropriate number of bits from the resulting
      // hash value are used to compose three output values: an encryption key, an authentication key, and a password verification value.
      // The first n bits become the encryption key, the next m bits become the authentication key, and the last 16 bits (two bytes) become
      // the password verification value.
      // As part of the process outlined in RFC 2898 a pseudo-random function must be called; AE-2 uses the HMAC-SHA1 function, since it is a
      // well-respected algorithm that has been in wide use for this purpose for several years.
      // Note that, when used in connection with 192- or 256-bit AES encryption, the fact that HMAC-SHA1 produces a 160-bit result means that,
      // regardless of the password that you specify, the search space for the encryption key is unlikely to reach the theoretical 192- or
      // 256-bit maximum, and cannot be guaranteed to exceed 160 bits. This is discussed in section B.1.1 of the RFC2898 specification.

      final PBEParametersGenerator generator = new PKCS5S2ParametersGenerator();
      generator.init(passwordBytes, saltBytes, 1000);

      // This is the one we'll actually use to decrypt using Olafs method
      aesCipherParameters = generator.generateDerivedParameters(aesKeyBitLength);

      // This is to get the keygeneration
      final CipherParameters cipherParameters = generator.generateDerivedParameters(aesKeyBitLength * 2 + 16);
      final byte[] keyBytes = ((KeyParameter) cipherParameters).getKey();

      cryptoKeyBytes = new byte[16];
      System.arraycopy(keyBytes, 0, cryptoKeyBytes, 0, 16);

      // Password verification value
      // This two-byte value is produced as part of the process that derives the encryption and decryption keys from the password.
      // When encrypting, a verification value is derived from the encryption password and stored with the encrypted file.
      // Before decrypting, a verification value can be derived from the decryption password and compared to the value stored with the file,
      // serving as a quick check that will detect most, but not all, incorrect passwords. There is a 1 in 65,536 chance that an incorrect
      // password will yield a matching verification value; therefore, a matching verification value cannot be absolutely relied on to
      // indicate a correct password.
      // This value is stored unencrypted.
      final byte[] pwVerificationBytes = new byte[2];
      System.arraycopy(keyBytes, (aesKeyBitLength / 8) * 2, pwVerificationBytes, 0, 2);
      if (!isEqual(pwVerificationBytes, passwordVerificationBytes))

      { throw new ZipException("wrong password - " + byteArrayToHexString(pwVerificationBytes) + "/ " + byteArrayToHexString(passwordVerificationBytes)); }

      }

      @Override
      public int read() throws IOException

      { return baseInputStream.read(); }

      private void setupInputStream(ZipArchiveEntry zipEntry, InputStream is) throws Exception {

      // File format
      // Additional overhead data required for decryption is stored with the encrypted file itself (i.e., not in the headers). The actual format of the stored file is as follows; additional information about these fields is below. All fields are byte-aligned.
      // Size (bytes) - Content
      // -----------------------
      // n bytes - Salt value is variable depending on encryption strength
      // 2 bytes - Password verification value
      // m bytes - Encrypted file data where m = zipEntry.getCompressedSize() - authentication (10) - salt - verification (2)
      // 10 bytes - Authentication code

      // Since we grab the salt and password verification in initialize we've already read some bytes from the stream so instead of:
      // bytesLeftToRead = zipEntry.getCompressedSize() - authentication (10) - salt - verification (2);
      // we use
      // bytesLeftToRead = zipEntry.getCompressedSize() - authentication (10);
      int bytesLeftToRead = (int) zipEntry.getCompressedSize() - 10;
      // See initialize
      if (compressionType == ExtendedZipArchiveEntry.COMPRESSION_METHOD_00_STORED)

      { bytesLeftToRead = (int) zipEntry.getSize(); }

      // System.out.println("AES Encryption Strength=" + "0x0" + Integer.toHexString(aesEncyrptionStrength));
      // System.out.println("Password=" + ByteArrayHelper.toString(passwd.getBytes()));
      // System.out.println("Salt=" + ByteArrayHelper.toString(salt));
      // System.out.println("Verification=" + ByteArrayHelper.toString(pwVerification));
      // System.out.println("Compressed Size=" + zipEntry.getCompressedSize());
      // System.out.println("Remaining=" + remaining);
      // System.out.println("Offset=" + offset);

      baseInputStream = new WrappedWinzipAesDecryptedInputStream(bytesLeftToRead, is);
      if (compressionType == ExtendedZipArchiveEntry.COMPRESSION_METHOD_00_STORED) {

      } else if (compressionType == ExtendedZipArchiveEntry.COMPRESSION_METHOD_08_DEFLATED)

      { baseInputStream = new InflaterInputStream(baseInputStream, new Inflater(true)); }

      else

      { throw new Exception("Unhandled compression type: " + compressionType); }

      }

      }
      ---------------
      6. ZipCryptoInputStream
      ---------------
      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)
      // 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 ^ decrypt_byte()
      // update_keys(C)
      // buffer <- 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)); }

      }

      Attachments

        1. code.zip
          79 kB
          Lloyd Gomes
        2. PkwareEncryptionAware.java
          6 kB
          Bear Giles
        3. PkwareEncryptionAware.java
          6 kB
          Bear Giles
        4. PkwareEncryptionAware.java
          6 kB
          Bear Giles

        Issue Links

          Activity

            People

              Unassigned Unassigned
              gomes_la Lloyd Gomes
              Votes:
              4 Vote for this issue
              Watchers:
              6 Start watching this issue

              Dates

                Created:
                Updated: