1 importjava.io.IOException;2 importjava.io.OutputStream;3 importjava.security.SecureRandom;4 importjava.util.ArrayList;5 importjava.util.Arrays;6 importstaticresearch.zip.ZipUtil.*;7 8 /**9 * Output stream that can be used to password-protect zip files.10 *11 *
Example usage:
12 *13 * Creating a password-protected zip file:14 *
15 *16 *17 * ZipEncryptOutputStream zeos = new ZipEncryptOutputStream(new FileOutputStream(fileName), password);18 * ZipOutputStream zos = new ZipOuputStream(zdis);19 *
create zip file using the standard JDK ZipOutputStream in zos variable
20 *
21 *22 * Converting a plain zip file to a password-protected zip file:23 *
24 *25 *26 * FileInputStream src = new FileInputStream( srcFile );27 * ZipEncryptOutputStream dest = new ZipEncryptOutputStream( new FileOutputStream( destFile ), password );28 *29 * // should wrap with try-catch-finally, do the close in finally30 * int b;31 * while (( b = src.read() ) > -1) {32 * dest.write( b );33 * }34 *35 * src.close();36 * dest.close();37 *38 *39 *@authorMartin Matula (martin at alutam.com)40 */41 publicclassZipEncryptOutputStreamextendsOutputStream {42 privatefinalOutputStream delegate;43 privatefinalintkeys[]=newint[3];44 privatefinalintpwdKeys[]=newint[3];45 46 privateintcopyBytes;47 privateintskipBytes;48 privateState state=State.NEW_SECTION;49 privateState futureState;50 privateSection section;51 privatebyte[] decryptHeader;52 privatefinalArrayListcrcAndSize=newArrayList();53 privatefinalArrayListlocalHeaderOffset=newArrayList();54 privateArrayListfileData;55 privateint[][] condition;56 privateintfileIndex;57 privateint[] buffer;58 privateintbufOffset;59 privateintfileSize;60 privateintbytesWritten;61 privateintcentralRepoOffset;62 63 privatestaticfinalintROW_SIZE=65536;64 65 /**66 * Convenience constructor taking password as a string.67 *68 *@paramdelegate69 * Output stream to write the password-protected zip to.70 *@parampassword71 * Password to use for protecting the zip.72 */73 publicZipEncryptOutputStream( OutputStream delegate, String password ) {74 this( delegate, password.toCharArray() );75 }76 77 /**78 * Safer version of the constructor. Takes password as a char array that can79 * be nulled right after calling this constructor instead of a string that80 * may stay visible on the heap for the duration of application run time.81 *82 *@paramdelegate83 * Output stream to write the password-protected zip to.84 *@parampassword85 * Password to use for protecting the zip.86 */87 publicZipEncryptOutputStream( OutputStream delegate,char[] password ) {88 this.delegate=delegate;89 pwdKeys[0]=305419896;90 pwdKeys[1]=591751049;91 pwdKeys[2]=878082192;92 for(inti=0; i0) {108 skipBytes--;109 return;110 }111 if( copyBytes==0) {112 switch(state) {113 caseNEW_SECTION:114 if( b!=0x50) {115 thrownewIllegalStateException("Unexpected value read at offset"+bytesWritten+":"+b+"(expected:"+0x50+")");116 }117 buffer(newint[4], State.SECTION_HEADER,0x50);118 return;119 caseSECTION_HEADER:120 identifySectionHeader();121 break;122 caseFLAGS:123 copyBytes=7;124 state=State.CRC;125 if( section==Section.LFH ) {126 if( ( b&1)==1) {127 thrownewIllegalStateException("ZIP already password protected.");128 }129 if( ( b&64)==64) {130 thrownewIllegalStateException("Strong encryption used.");131 }132 if( ( b&8)==8) {133 bufferUntil( State.FILE_BUFFERED, CFH_SIGNATURE, LFH_SIGNATURE );134 }135 }136 b=b&0xf7|1;137 break;138 caseCRC:139 if( section==Section.CFH ) {140 int[][] cns=crcAndSize.get( fileIndex );141 for(intj=0; j<3; j++) {142 for(inti=0; i<4; i++) {143 writeToDelegate( cns[j][i] );144 }145 }146 skipBytes=11;147 copyBytes=14;148 state=State.FILE_HEADER_OFFSET;149 }else{150 int[] cns=newint[16];151 buffer( cns, State.COMPRESSED_SIZE_READ, b );152 }153 return;154 caseFILE_HEADER_OFFSET:155 writeAsBytes( localHeaderOffset.get( fileIndex ) );156 fileIndex++;157 skipBytes=3;158 copyBytesUntil( State.SECTION_HEADER, CFH_SIGNATURE, ECD_SIGNATURE );159 return;160 caseCOMPRESSED_SIZE_READ:161 int[][] cns=newint[][] {162 { buffer[0], buffer[1], buffer[2], buffer[3] },163 { buffer[4], buffer[5], buffer[6], buffer[7] },164 { buffer[8], buffer[9], buffer[10], buffer[11] } };165 adjustSize( cns[1] );166 crcAndSize.add( cns );167 for(intj=0; j<3; j++) {168 for(inti=0; i<4; i++) {169 writeToDelegate( cns[j][i] );170 }171 }172 copyBytes=buffer[12]+buffer[14]+( buffer[13]+buffer[15] )*256-1;173 state=State.HEADER;174 if( copyBytes<0) {175 thrownewIllegalStateException("No file name stored in the zip file.");176 }177 break;178 caseHEADER:179 writeDecryptHeader();180 fileSize=decode( crcAndSize.get( crcAndSize.size()-1)[1] );181 state=State.DATA;182 //intentionally no break183 caseDATA:184 b=encrypt( b );185 fileSize--;186 if( fileSize==0) {187 state=State.NEW_SECTION;188 }189 break;190 caseBUFFER:191 buffer[bufOffset]=b;192 bufOffset++;193 if( bufOffset==buffer.length ) {194 state=futureState;195 }196 return;197 caseBUFFER_COPY:198 buffer[bufOffset]=b;199 if( checkCondition() ) {200 bufOffset=0;201 state=futureState;202 }203 break;204 caseBUFFER_UNTIL:205 intcol=fileSize%ROW_SIZE;206 if( col==0) {207 fileData.add(newint[ROW_SIZE] );208 }209 int[] row=fileData.get( fileData.size()-1);210 row[col]=b;211 buffer[bufOffset]=b;212 fileSize++;213 if( checkCondition() ) {214 fileSize-=buffer.length;215 state=futureState;216 }217 return;218 caseFILE_BUFFERED:219 row=fileData.get(0);220 intr=0;221 intpointer=16+row[12]+row[14]+( row[13]+row[15] )*256;222 cns=newint[3][4];223 readFromFileBuffer( fileSize-12, cns[0] );224 readFromFileBuffer( fileSize-8, cns[1] );225 readFromFileBuffer( fileSize-4, cns[2] );226 fileSize=decode( cns[1] );227 adjustSize( cns[1] );228 crcAndSize.add( cns );229 for(inti=0; i<4; i++) {230 row[i]=cns[0][i];231 row[i+4]=cns[1][i];232 row[i+8]=cns[2][i];233 }234 for(inti=0; i>8;274 values[i]&=0xff;275 }276 }277 278 privatestaticintdecode(int[] value ) {279 returnvalue[0]+( value[1]<<8)+( value[2]<<16)+( value[3]<<24);280 }281 282 privatevoidwriteAsBytes(intvalue )throwsIOException {283 for(inti=0; i<4; i++) {284 writeToDelegate( value&0xff);285 value>>=8;286 }287 }288 289 privatevoididentifySectionHeader()throwsIllegalStateException,290 IOException {291 if( Arrays.equals( buffer, LFH_SIGNATURE ) ) {292 section=Section.LFH;293 copyBytes=1;294 state=State.FLAGS;295 localHeaderOffset.add( bytesWritten );296 }elseif( Arrays.equals( buffer, CFH_SIGNATURE ) ) {297 section=Section.CFH;298 copyBytes=3;299 state=State.FLAGS;300 if( centralRepoOffset==0) {301 centralRepoOffset=bytesWritten;302 }303 }elseif( Arrays.equals( buffer, ECD_SIGNATURE ) ) {304 section=Section.ECD;305 copyBytes=11;306 state=State.REPO_OFFSET;307 }else{308 thrownewIllegalStateException("Unknown header:"+Arrays.asList( buffer ).toString() );309 }310 flushBuffer();311 }312 313 privatevoidreadFromFileBuffer(intoffset,int[] target ) {314 intr=offset/ROW_SIZE;315 intc=offset%ROW_SIZE;316 int[] row=fileData.get( r );317 for(inti=0; i>>8);345 }346 347 privateintencrypt(intb ) {348 intnewB=( b^encryptByte() )&0xff;349 updateKeys( (byte) b );350 returnnewB;351 }352 353 privatevoidwriteDecryptHeader()throwsIOException {354 initKeys();355 int[] crc=crcAndSize.get( crcAndSize.size()-1)[0];356 SecureRandom random=newSecureRandom();357 decryptHeader=newbyte[DECRYPT_HEADER_SIZE];358 random.nextBytes( decryptHeader );359 decryptHeader[DECRYPT_HEADER_SIZE-2]=(byte) crc[2];360 decryptHeader[DECRYPT_HEADER_SIZE-1]=(byte) crc[3];361 for(inti=0; i knownValues ) {367 System.arraycopy( knownValues,0, values,0, knownValues.length );368 buffer=values;369 bufOffset=knownValues.length;370 this.state=State.BUFFER;371 futureState=state;372 }373 374 privatevoidflushBuffer()throwsIOException {375 for(inti=0; i
condition ) {381 futureState=state;382 this.condition=condition;383 bufOffset=0;384 buffer=newint[condition[0].length];385 this.state=State.BUFFER_COPY;386 }387 388 privatevoidbufferUntil( State state,int[]
condition ) {389 copyBytesUntil( state, condition );390 fileData=newArrayList();391 fileSize=0;392 this.state=State.BUFFER_UNTIL;393 }394 395 privatebooleancheckCondition() {396 booleanequals=true;397 for(inti=0; i