Package com.zwl.util.zip

Source Code of com.zwl.util.zip.AesZipFileDecrypter

package com.zwl.util.zip;



import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.DataFormatException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import com.zwl.util.zip.impl.AESDecrypter;
import com.zwl.util.zip.impl.AESDecrypterBC;
import com.zwl.util.zip.impl.ByteArrayHelper;
import com.zwl.util.zip.impl.CentralDirectoryEntry;
import com.zwl.util.zip.impl.ExtRandomAccessFile;
import com.zwl.util.zip.impl.ExtZipEntry;
import com.zwl.util.zip.impl.ExtZipOutputStream;
import com.zwl.util.zip.impl.ZipConstants;

/**
* List/Extract data from AES encrypted WinZip file (readOnly).
*
* TODO - support 128 + 192 keys
*
* @see http://www.winzip.com/aes_info.htm
*
* @author olaf@merkert.de
*/
public class AesZipFileDecrypter implements ZipConstants {

  private static final Logger LOG = Logger.getLogger( AesZipFileDecrypter.class.getName() );

  // --------------------------------------------------------------------------

  /** charset to use for filename(s) and password - defaults to iso-8859-1 */
  public static String charset = "iso-8859-1";

  /** size of buffer to use for byte[] operations - defaults to 1024 */
  protected static int bufferSize = 1024 * 10;

  // --------------------------------------------------------------------------

  /** random access file to access the archive data */
  protected ExtRandomAccessFile raFile;

  /** where does the directory (after file data) start? */
  protected long dirOffsetPos;

  protected File zipFile;
 
  public AesZipFileDecrypter( File zipFile ) throws IOException {
    this.zipFile = zipFile;
    this.raFile = new ExtRandomAccessFile( zipFile );
    this.dirOffsetPos = zipFile.length() - 6;
  }

  public void close() throws IOException {
    raFile.close();
  }

  // --------------------------------------------------------------------------

  public List<ExtZipEntry> getEntryList() throws IOException, ZipException {
    List<ExtZipEntry> out = new ArrayList<ExtZipEntry>();

    short totalNumberOfEntries = this.getNumberOfEntries();
    int dirOffset = raFile.readInt( this.dirOffsetPos );

    long fileOffset = dirOffset;
    for( int i=0; i<totalNumberOfEntries; i++ ) {
      int censig = raFile.readInt( fileOffset );
      if( censig!=CENSIG ) {
        throw new ZipException("expected CENSIC not found at entry no " + (i+1) + " in central directory at end of zip file at " + fileOffset);
      }
     
      short fileNameLength = raFile.readShort( fileOffset + 28 );
      short extraFieldLength = raFile.readShort( fileOffset + 30 );
      long fileOffsetPos = fileOffset + 28 + 14;
      long fileDataOffset = raFile.readInt( fileOffsetPos );
      int locsig = raFile.readInt( fileDataOffset );
      if( locsig!=LOCSIG ) {
        //throw new ZipException("expected LOCSIC not found at alleged position of data for file no " + (i+1));
        System.out.println( fileOffsetPos + " - " + fileNameLength );
      }

      byte[] fileNameBytes = raFile.readByteArray( fileOffsetPos+4, fileNameLength );
      long nextFileOffset = raFile.getFilePointer();
      String fileName = new String( fileNameBytes, charset );
     
      ExtZipEntry zipEntry = new ExtZipEntry( fileName );

      CentralDirectoryEntry cde = new CentralDirectoryEntry( raFile, fileOffset );
      zipEntry.setCentralDirectoryEntry( cde );

      zipEntry.setCompressedSize( cde.getCompressedSize() );
      zipEntry.setSize( cde.getUncompressedSize() );

      long dosTime = raFile.readInt( fileOffset + 12 );
      zipEntry.setTime( ExtZipEntry.dosToJavaTime(dosTime) );
     
      if( cde.isEncrypted() ) {
        zipEntry.setMethod( cde.getActualCompressionMethod() );
        zipEntry.setOffset( (int)(cde.getLocalHeaderOffset() + cde.getLocalHeaderSize()) + cde.getCryptoHeaderLength() );
        zipEntry.initEncryptedEntry();
      } else {
        zipEntry.setMethod( ZipEntry.DEFLATED );
        zipEntry.setPrimaryCompressionMethod( ZipEntry.DEFLATED );
      }

      nextFileOffset += extraFieldLength;

      out.add(zipEntry);

      fileOffset = nextFileOffset;
    }

    return out;
  }

  public ExtZipEntry getEntry( String name ) throws IOException, ZipException, DataFormatException {
    for( ExtZipEntry zipEntry : getEntryList() ) {
      if( name.equals(zipEntry.getName()) ) {
        return zipEntry;
      }
    }
    return null;
  }

  public void extractEntry( ExtZipEntry zipEntry, File outFile, String password ) throws IOException, ZipException, DataFormatException {
    if( zipEntry==null ) {
      throw new ZipException("zipEntry must NOT be NULL");
    }
    if( zipEntry.isEncrypted() ) {
      byte[] pwBytes = password.getBytes(charset);
     
      CentralDirectoryEntry cde = zipEntry.getCentralDirectoryEntry();
      int cryptoHeaderOffset = zipEntry.getOffset() - cde.getCryptoHeaderLength();
     
      byte[] salt = raFile.readByteArray( cryptoHeaderOffset, 16 );
      byte[] pwVerification = raFile.readByteArray( cryptoHeaderOffset+16, 2 );

      if( LOG.isLoggable(Level.FINEST) ) {
        LOG.finest( "\n" + cde.toString() );
        LOG.finest( "offset    = " + zipEntry.getOffset() );
        LOG.finest( "cryptoOff = " + cryptoHeaderOffset );
        LOG.finest( "pwBytes   = " + ByteArrayHelper.toString(pwBytes) + " - " + pwBytes.length );
        LOG.finest( "salt      = " + ByteArrayHelper.toString(salt) + " - " + salt.length );
        LOG.finest( "pwVerif   = " + ByteArrayHelper.toString(pwVerification) + " - " + pwVerification.length );
      }
     
      // encrypter throws ZipException for wrong password
      AESDecrypter decrypter = new AESDecrypterBC( pwBytes, salt, pwVerification );

      // create tmp file to contains the decrypted, but still compressed data
      File tmpFile = new File( outFile.getPath() + "_TMP.zip" );
      makeDir( new File(tmpFile.getParent()) );
      ExtZipOutputStream zos = new ExtZipOutputStream( tmpFile );
      ExtZipEntry tmpEntry = new ExtZipEntry( zipEntry );
      tmpEntry.setPrimaryCompressionMethod( zipEntry.getMethod() );
      zos.putNextEntry( tmpEntry );

      raFile.seek( cde.getOffset() );
      byte[] buffer = new byte[bufferSize];
      int remaining = (int)zipEntry.getEncryptedDataSize();
      while( remaining>0 ) {
        int len = (remaining>buffer.length) ? buffer.length : remaining;
        int read = raFile.readByteArray(buffer,len);
        decrypter.decrypt( buffer, read );
        zos.writeBytes( buffer, 0, read );
        remaining -= len;
      }
      zos.finish();

      byte[] storedMac = new byte[10];
      raFile.readByteArray(storedMac,10);
      byte[] calcMac = decrypter.getFinalAuthentication();
      if( LOG.isLoggable(Level.FINE) ) {
        LOG.fine"storedMac=" + Arrays.toString(storedMac) );
        LOG.fine"calcMac=" + Arrays.toString(calcMac) );
      }
      if( !Arrays.equals(storedMac, calcMac ) ) {
        throw new ZipException("stored authentication (mac) value does not match calculated one");
      }

      ZipFile zf = new ZipFile( tmpFile );
      ZipEntry ze = zf.entries().nextElement();
      InputStream is = zf.getInputStream( ze );
      FileOutputStream fos = new FileOutputStream ( outFile.getPath() );
      int read = is.read( buffer );
      while( read>0 ) {
        fos.write( buffer, 0, read );
        read = is.read( buffer );
      }
      fos.close();
      is.close();
      zf.close();

      tmpFile.delete();
    } else {
      throw new ZipException( "currently only extracts encrypted files - use java.util.zip to unzip" );
    }
  }

  /** number of entries in file (files AND directories) */
  public short getNumberOfEntries() throws IOException {
    return raFile.readShort( this.dirOffsetPos-6 );
  }

  protected static void makeDir(File dir) {
    if( dir!=null ) {
      if( !dir.exists() ) {
        if( dir.getParent()!=null ) {
          File parentDir = new File(dir.getParent());
          if( !parentDir.exists() ) {
            makeDir(parentDir);
          }
        }
        dir.mkdir();
      }
    }
  }
 
  // --------------------------------------------------------------------------

  /** testcode + usage example */
  public static void main( String[] args ) throws Exception {
    //LogManager.getLogManager().readConfiguration( new FileInputStream("logging.properties") );
    AesZipFileDecrypter zipFile = new AesZipFileDecrypter( new File("doc/zipSpecificationAes.zip") );
    ExtZipEntry entry = zipFile.getEntry( "zipSpecification.txt" );
    zipFile.extractEntry( entry, new File("doc/zipSpecification.txt"), "foo" );
  }

}
TOP

Related Classes of com.zwl.util.zip.AesZipFileDecrypter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.