[
https://issues.apache.org/jira/browse/COMPRESS-88?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15038516#comment-15038516
]
Stefan Bodewig commented on COMPRESS-88:
----------------------------------------
In case anybody else stumbles over
http://www.prnewswire.com/news-releases/pkware-announces-free-licensing-program-for-secure-ziptm-and-pkzip-reader-technologies-72382192.html
- I've contacted PKWare and asked them about it and was told, the program
doesn't exist anymore.
> Adding encryption support to ZipFIle
> ------------------------------------
>
> Key: COMPRESS-88
> URL: https://issues.apache.org/jira/browse/COMPRESS-88
> Project: Commons Compress
> Issue Type: Wish
> Components: Archivers
> Environment: All
> Reporter: Lloyd Gomes
> Priority: Minor
> Labels: API, zip
> Attachments: PkwareEncryptionAware.java, PkwareEncryptionAware.java,
> PkwareEncryptionAware.java, code.zip
>
>
> 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) {
> 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(off);
> 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:[email protected]">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 (n) - 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 (n) - 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 (n) - 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(i))
> // end loop
> //
> // Where update_keys() is defined as:
> //
> // update_keys(char):
> // Key(0) <- crc32(key(0),char)
> // Key(1) <- Key(1) + (Key(0) & 000000ffH)
> // Key(1) <- Key(1) * 134775813 + 1
> // Key(2) <- crc32(key(2),key(1) >> 24)
> // end update_keys
> //
> // Where crc32(old_crc,char) is a routine that given a CRC
> value and a
> // character, returns an updated CRC value after applying the
> CRC-32
> // algorithm described elsewhere in this document.
> for (int i = 0; i < passwd.length(); i++) {
> update_keys((byte) passwd.charAt(i));
> }
> // Step 2 - Decrypting the encryption header
> // -----------------------------------------
> //
> // The purpose of this step is to further initialize the
> encryption
> // keys, based on random data, to render a plaintext attack on
> the
> // data ineffective.
> //
> // Read the 12-byte encryption header into Buffer, in locations
> // Buffer(0) thru Buffer(11).
> //
> // loop for i <- 0 to 11
> // C <- buffer(i) ^ decrypt_byte()
> // update_keys(C)
> // buffer(i) <- C
> // end loop
> //
> // Where decrypt_byte() is defined as:
> //
> // unsigned char decrypt_byte()
> // local unsigned short temp
> // temp <- Key(2) | 2
> // decrypt_byte <- (temp * (temp ^ 1)) >> 8
> // end decrypt_byte
> //
> final byte[] encryptionHeader = new byte[12];
> for (int i = 0; i < 12; i++) {
> encryptionHeader[i] = (byte) read();
> }
> // After the header is decrypted, the last 1 or 2 bytes in
> Buffer
> // should be the high-order word/byte of the CRC for the file
> being
> // decrypted, stored in Intel low-byte/high-byte order.
> Versions of
> // PKZIP prior to 2.0 used a 2 byte CRC check; a 1 byte CRC
> check is
> // used on versions after 2.0. This can be used to test if the
> password
> // supplied is correct or not.
> byte[] passwordCheck = new byte[] { encryptionHeader[11], 0, 0,
> 0, 0, 0, 0, 0 };
> long suppliedPasswordCheck =
> EndianUtils.readSwappedLong(passwordCheck, 0);
> long actualPasswordCheck = zipEntry.getCrc() & 0xff000000;
> actualPasswordCheck = actualPasswordCheck >> 24;
> if (actualPasswordCheck != suppliedPasswordCheck) {
> throw new ZipException("Invalid password specified");
> }
> }
> private short decrypt_byte() {
> int t = (int) ((keys[2] & 0xFFFF) | 2);
> return (short) ((t * (t ^ 1)) >> 8);
> }
> @Override
> public int read() throws IOException {
> // Step 3 - Decrypting the compressed data stream
> // ----------------------------------------------
> //
> // The compressed data stream can be decrypted as follows:
> //
> // loop until done
> // read a character into C
> // Temp <- C ^ decrypt_byte()
> // update_keys(temp)
> // output Temp
> // end loop
> int c = baseInputStream.read();
> if (c != -1) {
> c = c ^ decrypt_byte();
> update_keys((byte) c);
> c = c & 0xffff;
> }
> return c;
> }
> private void update_keys(short byteValue) {
> keys[0] = crc32(keys[0], byteValue);
> keys[1] = keys[1] + (keys[0] & 0x000000ffl);
> keys[1] = (keys[1] * 134775813) + 1;
> keys[2] = crc32(keys[2], (byte) (keys[1] >> 24));
> }
> }
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)