|
Posted on 2015-08-19 10:16 云自无心水自闲 阅读(9940) 评论(0) 编辑 收藏
以下是源代码,使用说明包含在文件头部的注释中。
1.
1 import java.io.IOException;
2 import java.io.InputStream;
3
4 import static ZipUtil.*;
5
6 /**
7 * Input stream converting a password-protected zip to an unprotected zip.
8 *
9 * <h3>Example usage:</h3>
10 * <p>Reading a password-protected zip from file:</p>
11 * <pre>
12 * ZipDecryptInputStream zdis = new ZipDecryptInputStream(new FileInputStream(fileName), password);
13 * ZipInputStream zis = new ZipInputStream(zdis);
14 * read the zip file from zis - the standard JDK ZipInputStream
15 * </pre>
16 * <p>Converting a password-protected zip file to an unprotected zip file:</p>
17 * <pre>
18 * ZipDecryptInputStream src = new ZipDecryptInputStream(new FileInputStream(srcFile), password);
19 * FileOutputStream dest = new FileOutputStream(destFile);
20 *
21 * // should wrap with try-catch-finally, do the close in finally
22 * int b;
23 * while ((b = src.read()) > -1) {
24 * dest.write(b);
25 * }
26 *
27 * src.close();
28 * dest.close();
29 * </pre>
30 *
31 * @author Martin Matula (martin at alutam.com)
32 */
33 public class ZipDecryptInputStream extends InputStream {
34 private final InputStream delegate;
35 private final int keys[] = new int[3];
36 private final int pwdKeys[] = new int[3];
37
38 private State state = State.SIGNATURE;
39 private boolean isEncrypted;
40 private Section section;
41 private int skipBytes;
42 private int compressedSize;
43 private int crc;
44
45 /**
46 * Creates a new instance of the stream.
47 *
48 * @param stream Input stream serving the password-protected zip file to be decrypted.
49 * @param password Password to be used to decrypt the password-protected zip file.
50 */
51 public ZipDecryptInputStream(InputStream stream, String password) {
52 this(stream, password.toCharArray());
53 }
54
55 /**
56 * Safer constructor. Takes password as a char array that can be nulled right after
57 * calling this constructor instead of a string that may be visible on the heap for
58 * the duration of application run time.
59 *
60 * @param stream Input stream serving the password-protected zip file.
61 * @param password Password to use for decrypting the zip file.
62 */
63 public ZipDecryptInputStream(InputStream stream, char[] password) {
64 this.delegate = stream;
65 pwdKeys[0] = 305419896;
66 pwdKeys[1] = 591751049;
67 pwdKeys[2] = 878082192;
68 for (int i = 0; i < password.length; i++) {
69 ZipUtil.updateKeys((byte) (password[i] & 0xff), pwdKeys);
70 }
71 }
72
73 @Override
74 public int read() throws IOException {
75 int result = delegateRead();
76 if (skipBytes == 0) {
77 switch (state) {
78 case SIGNATURE:
79 if (!peekAheadEquals(LFH_SIGNATURE)) {
80 state = State.TAIL;
81 } else {
82 section = Section.FILE_HEADER;
83 skipBytes = 5;
84 state = State.FLAGS;
85 }
86 break;
87 case FLAGS:
88 isEncrypted = (result & 1) != 0;
89 if ((result & 64) == 64) {
90 throw new IllegalStateException("Strong encryption used.");
91 }
92 if ((result & 8) == 8) {
93 compressedSize = -1;
94 state = State.FN_LENGTH;
95 skipBytes = 19;
96 } else {
97 state = State.CRC;
98 skipBytes = 10;
99 }
100 if (isEncrypted) {
101 result -= 1;
102 }
103 break;
104 case CRC:
105 crc = result;
106 state = State.COMPRESSED_SIZE;
107 break;
108 case COMPRESSED_SIZE:
109 int[] values = new int[4];
110 peekAhead(values);
111 compressedSize = 0;
112 int valueInc = isEncrypted ? DECRYPT_HEADER_SIZE : 0;
113 for (int i = 0; i < 4; i++) {
114 compressedSize += values[i] << (8 * i);
115 values[i] -= valueInc;
116 if (values[i] < 0) {
117 valueInc = 1;
118 values[i] += 256;
119 } else {
120 valueInc = 0;
121 }
122 }
123 overrideBuffer(values);
124 result = values[0];
125 if (section == Section.DATA_DESCRIPTOR) {
126 state = State.SIGNATURE;
127 } else {
128 state = State.FN_LENGTH;
129 }
130 skipBytes = 7;
131 break;
132 case FN_LENGTH:
133 values = new int[4];
134 peekAhead(values);
135 skipBytes = 3 + values[0] + values[2] + (values[1] + values[3]) * 256;
136 if (!isEncrypted) {
137 if (compressedSize > 0) {
138 throw new IllegalStateException("ZIP not password protected.");
139 }
140 state = State.SIGNATURE;
141 } else {
142 state = State.HEADER;
143 }
144 break;
145 case HEADER:
146 section = Section.FILE_DATA;
147 initKeys();
148 byte lastValue = 0;
149 for (int i = 0; i < DECRYPT_HEADER_SIZE; i++) {
150 lastValue = (byte) (result ^ decryptByte());
151 updateKeys(lastValue);
152 result = delegateRead();
153 }
154 if ((lastValue & 0xff) != crc) {
155 // throw new IllegalStateException("Wrong password!");
156 }
157 compressedSize -= DECRYPT_HEADER_SIZE;
158 state = State.DATA;
159 // intentionally no break
160 case DATA:
161 if (compressedSize == -1 && peekAheadEquals(DD_SIGNATURE)) {
162 section = Section.DATA_DESCRIPTOR;
163 skipBytes = 5;
164 state = State.CRC;
165 } else {
166 result = (result ^ decryptByte()) & 0xff;
167 updateKeys((byte) result);
168 compressedSize--;
169 if (compressedSize == 0) {
170 state = State.SIGNATURE;
171 }
172 }
173 break;
174 case TAIL:
175 // do nothing
176 }
177 } else {
178 skipBytes--;
179 }
180 return result;
181 }
182
183 private static final int BUF_SIZE = 8;
184 private int bufOffset = BUF_SIZE;
185 private final int[] buf = new int[BUF_SIZE];
186
187 private int delegateRead() throws IOException {
188 bufOffset++;
189 if (bufOffset >= BUF_SIZE) {
190 fetchData(0);
191 bufOffset = 0;
192 }
193 return buf[bufOffset];
194 }
195
196 private boolean peekAheadEquals(int[] values) throws IOException {
197 prepareBuffer(values);
198 for (int i = 0; i < values.length; i++) {
199 if (buf[bufOffset + i] != values[i]) {
200 return false;
201 }
202 }
203 return true;
204 }
205
206 private void prepareBuffer(int[] values) throws IOException {
207 if (values.length > (BUF_SIZE - bufOffset)) {
208 for (int i = bufOffset; i < BUF_SIZE; i++) {
209 buf[i - bufOffset] = buf[i];
210 }
211 fetchData(BUF_SIZE - bufOffset);
212 bufOffset = 0;
213 }
214 }
215
216 private void peekAhead(int[] values) throws IOException {
217 prepareBuffer(values);
218 System.arraycopy(buf, bufOffset, values, 0, values.length);
219 }
220
221 private void overrideBuffer(int[] values) throws IOException {
222 prepareBuffer(values);
223 System.arraycopy(values, 0, buf, bufOffset, values.length);
224 }
225
226 private void fetchData(int offset) throws IOException {
227 for (int i = offset; i < BUF_SIZE; i++) {
228 buf[i] = delegate.read();
229 if (buf[i] == -1) {
230 break;
231 }
232 }
233 }
234
235 @Override
236 public void close() throws IOException {
237 delegate.close();
238 super.close();
239 }
240
241 private void initKeys() {
242 System.arraycopy(pwdKeys, 0, keys, 0, keys.length);
243 }
244
245 private void updateKeys(byte charAt) {
246 ZipUtil.updateKeys(charAt, keys);
247 }
248
249 private byte decryptByte() {
250 int temp = keys[2] | 2;
251 return (byte) ((temp * (temp ^ 1)) >>> 8);
252 }
253 }
2. 1 import java.io.IOException; 2 import java.io.OutputStream; 3 import java.security.SecureRandom; 4 import java.util.ArrayList; 5 import java.util.Arrays; 6 import static research.zip.ZipUtil.*; 7 8 /** 9 * Output stream that can be used to password-protect zip files. 10 * 11 * <h3>Example usage:</h3> 12 * <p> 13 * Creating a password-protected zip file: 14 * </p> 15 * 16 * <pre> 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 * </pre> 21 * <p> 22 * Converting a plain zip file to a password-protected zip file: 23 * </p> 24 * 25 * <pre> 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 finally 30 * int b; 31 * while (( b = src.read() ) > -1) { 32 * dest.write( b ); 33 * } 34 * 35 * src.close(); 36 * dest.close(); 37 * </pre> 38 * 39 * @author Martin Matula (martin at alutam.com) 40 */ 41 public class ZipEncryptOutputStream extends OutputStream { 42 private final OutputStream delegate; 43 private final int keys[] = new int[3]; 44 private final int pwdKeys[] = new int[3]; 45 46 private int copyBytes; 47 private int skipBytes; 48 private State state = State.NEW_SECTION; 49 private State futureState; 50 private Section section; 51 private byte[] decryptHeader; 52 private final ArrayList<int[][]> crcAndSize = new ArrayList<int[][]>(); 53 private final ArrayList<Integer> localHeaderOffset = new ArrayList<Integer>(); 54 private ArrayList<int[]> fileData; 55 private int[][] condition; 56 private int fileIndex; 57 private int[] buffer; 58 private int bufOffset; 59 private int fileSize; 60 private int bytesWritten; 61 private int centralRepoOffset; 62 63 private static final int ROW_SIZE = 65536; 64 65 /** 66 * Convenience constructor taking password as a string. 67 * 68 * @param delegate 69 * Output stream to write the password-protected zip to. 70 * @param password 71 * Password to use for protecting the zip. 72 */ 73 public ZipEncryptOutputStream( 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 can 79 * be nulled right after calling this constructor instead of a string that 80 * may stay visible on the heap for the duration of application run time. 81 * 82 * @param delegate 83 * Output stream to write the password-protected zip to. 84 * @param password 85 * Password to use for protecting the zip. 86 */ 87 public ZipEncryptOutputStream( OutputStream delegate, char[] password ) { 88 this.delegate = delegate; 89 pwdKeys[0] = 305419896; 90 pwdKeys[1] = 591751049; 91 pwdKeys[2] = 878082192; 92 for ( int i = 0; i < password.length; i++ ) { 93 ZipUtil.updateKeys( (byte) ( password[i] & 0xff ), pwdKeys ); 94 } 95 } 96 97 private static enum State { 98 NEW_SECTION, SECTION_HEADER, FLAGS, REPO_OFFSET, CRC, FILE_HEADER_OFFSET, COMPRESSED_SIZE_READ, HEADER, DATA, FILE_BUFFERED, BUFFER, BUFFER_COPY, BUFFER_UNTIL, TAIL 99 } 100 101 private static enum Section { 102 LFH, CFH, ECD 103 } 104 105 @Override 106 public void write( int b ) throws IOException { 107 if ( skipBytes > 0 ) { 108 skipBytes--; 109 return; 110 } 111 if ( copyBytes == 0 ) { 112 switch (state) { 113 case NEW_SECTION: 114 if ( b != 0x50 ) { 115 throw new IllegalStateException( "Unexpected value read at offset " + bytesWritten + ": " + b + " (expected: " + 0x50 + ")" ); 116 } 117 buffer( new int[4], State.SECTION_HEADER, 0x50 ); 118 return; 119 case SECTION_HEADER: 120 identifySectionHeader(); 121 break; 122 case FLAGS: 123 copyBytes = 7; 124 state = State.CRC; 125 if ( section == Section.LFH ) { 126 if ( ( b & 1 ) == 1 ) { 127 throw new IllegalStateException( "ZIP already password protected." ); 128 } 129 if ( ( b & 64 ) == 64 ) { 130 throw new IllegalStateException( "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 case CRC: 139 if ( section == Section.CFH ) { 140 int[][] cns = crcAndSize.get( fileIndex ); 141 for ( int j = 0; j < 3; j++ ) { 142 for ( int i = 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 = new int[16]; 151 buffer( cns, State.COMPRESSED_SIZE_READ, b ); 152 } 153 return; 154 case FILE_HEADER_OFFSET: 155 writeAsBytes( localHeaderOffset.get( fileIndex ) ); 156 fileIndex++; 157 skipBytes = 3; 158 copyBytesUntil( State.SECTION_HEADER, CFH_SIGNATURE, ECD_SIGNATURE ); 159 return; 160 case COMPRESSED_SIZE_READ: 161 int[][] cns = new int[][] { 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 ( int j = 0; j < 3; j++ ) { 168 for ( int i = 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 throw new IllegalStateException( "No file name stored in the zip file." ); 176 } 177 break; 178 case HEADER: 179 writeDecryptHeader(); 180 fileSize = decode( crcAndSize.get( crcAndSize.size() - 1 )[1] ); 181 state = State.DATA; 182 // intentionally no break 183 case DATA: 184 b = encrypt( b ); 185 fileSize--; 186 if ( fileSize == 0 ) { 187 state = State.NEW_SECTION; 188 } 189 break; 190 case BUFFER: 191 buffer[bufOffset] = b; 192 bufOffset++; 193 if ( bufOffset == buffer.length ) { 194 state = futureState; 195 } 196 return; 197 case BUFFER_COPY: 198 buffer[bufOffset] = b; 199 if ( checkCondition() ) { 200 bufOffset = 0; 201 state = futureState; 202 } 203 break; 204 case BUFFER_UNTIL: 205 int col = fileSize % ROW_SIZE; 206 if ( col == 0 ) { 207 fileData.add( new int[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 case FILE_BUFFERED: 219 row = fileData.get( 0 ); 220 int r = 0; 221 int pointer = 16 + row[12] + row[14] + ( row[13] + row[15] ) * 256; 222 cns = new int[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 ( int i = 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 ( int i = 0; i < pointer; i++ ) { 235 writeToDelegate( row[i] ); 236 } 237 writeDecryptHeader(); 238 for ( int i = 0; i < fileSize; i++ ) { 239 writeToDelegate( encrypt( row[pointer] ) ); 240 pointer++; 241 if ( pointer == ROW_SIZE ) { 242 pointer = 0; 243 r++; 244 row = fileData.get( r ); 245 } 246 } 247 fileData = null; 248 identifySectionHeader(); 249 break; 250 case REPO_OFFSET: 251 writeAsBytes( centralRepoOffset ); 252 skipBytes = 3; 253 state = State.TAIL; 254 return; 255 case TAIL: 256 break; 257 } 258 } else { 259 copyBytes--; 260 } 261 writeToDelegate( b ); 262 } 263 264 private void writeToDelegate( int b ) throws IOException { 265 delegate.write( b ); 266 bytesWritten++; 267 } 268 269 private static void adjustSize( int[] values ) { 270 int inc = DECRYPT_HEADER_SIZE; 271 for ( int i = 0; i < 4; i++ ) { 272 values[i] = values[i] + inc; 273 inc = values[i] >> 8; 274 values[i] &= 0xff; 275 } 276 } 277 278 private static int decode( int[] value ) { 279 return value[0] + ( value[1] << 8 ) + ( value[2] << 16 ) + ( value[3] << 24 ); 280 } 281 282 private void writeAsBytes( int value ) throws IOException { 283 for ( int i = 0; i < 4; i++ ) { 284 writeToDelegate( value & 0xff ); 285 value >>= 8; 286 } 287 } 288 289 private void identifySectionHeader() throws IllegalStateException, 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 } else if ( 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 } else if ( Arrays.equals( buffer, ECD_SIGNATURE ) ) { 304 section = Section.ECD; 305 copyBytes = 11; 306 state = State.REPO_OFFSET; 307 } else { 308 throw new IllegalStateException( "Unknown header: " + Arrays.asList( buffer ).toString() ); 309 } 310 flushBuffer(); 311 } 312 313 private void readFromFileBuffer( int offset, int[] target ) { 314 int r = offset / ROW_SIZE; 315 int c = offset % ROW_SIZE; 316 int[] row = fileData.get( r ); 317 for ( int i = 0; i < target.length; i++ ) { 318 target[i] = row[c]; 319 c++; 320 if ( c == ROW_SIZE ) { 321 c = 0; 322 r++; 323 row = fileData.get( r ); 324 } 325 } 326 } 327 328 @Override 329 public void close() throws IOException { 330 super.close(); 331 delegate.close(); 332 } 333 334 private void initKeys() { 335 System.arraycopy( pwdKeys, 0, keys, 0, keys.length ); 336 } 337 338 private void updateKeys( byte charAt ) { 339 ZipUtil.updateKeys( charAt, keys ); 340 } 341 342 private byte encryptByte() { 343 int temp = keys[2] | 2; 344 return (byte) ( ( temp * ( temp ^ 1 ) ) >>> 8 ); 345 } 346 347 private int encrypt( int b ) { 348 int newB = ( b ^ encryptByte() ) & 0xff; 349 updateKeys( (byte) b ); 350 return newB; 351 } 352 353 private void writeDecryptHeader() throws IOException { 354 initKeys(); 355 int[] crc = crcAndSize.get( crcAndSize.size() - 1 )[0]; 356 SecureRandom random = new SecureRandom(); 357 decryptHeader = new byte[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 ( int i = 0; i < DECRYPT_HEADER_SIZE; i++ ) { 362 writeToDelegate( encrypt( decryptHeader[i] ) ); 363 } 364 } 365 366 private void buffer( int[] values, State state, int 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 private void flushBuffer() throws IOException { 375 for ( int i = 0; i < bufOffset; i++ ) { 376 writeToDelegate( buffer[i] ); 377 } 378 } 379 380 private void copyBytesUntil( State state, int[] condition ) { 381 futureState = state; 382 this.condition = condition; 383 bufOffset = 0; 384 buffer = new int[condition[0].length]; 385 this.state = State.BUFFER_COPY; 386 } 387 388 private void bufferUntil( State state, int[] condition ) { 389 copyBytesUntil( state, condition ); 390 fileData = new ArrayList<int[]>(); 391 fileSize = 0; 392 this.state = State.BUFFER_UNTIL; 393 } 394 395 private boolean checkCondition() { 396 boolean equals = true; 397 for ( int i = 0; i < condition.length; i++ ) { 398 equals = true; 399 for ( int j = 0; j <= bufOffset; j++ ) { 400 if ( condition[i][j] != buffer[j] ) { 401 equals = false; 402 break; 403 } 404 } 405 if ( equals ) { 406 bufOffset++; 407 break; 408 } 409 } 410 if ( !equals ) { 411 bufOffset = 0; 412 } 413 return equals && ( buffer.length == bufOffset ); 414 } 415 } 3. 1 /** 2 * 3 * @author Martin Matula (martin at alutam.com) 4 */ 5 class ZipUtil { 6 static final int[] CRC_TABLE = new int[256]; 7 // compute the table 8 // (could also have it pre-computed - see 9 // http://snippets.dzone.com/tag/crc32) 10 static { 11 for ( int i = 0; i < 256; i++ ) { 12 int r = i; 13 for ( int j = 0; j < 8; j++ ) { 14 if ( ( r & 1 ) == 1 ) { 15 r = ( r >>> 1 ) ^ 0xedb88320; 16 } else { 17 r >>>= 1; 18 } 19 } 20 CRC_TABLE[i] = r; 21 } 22 } 23 24 static final int DECRYPT_HEADER_SIZE = 12; 25 static final int[] CFH_SIGNATURE = { 0x50, 0x4b, 0x01, 0x02 }; 26 static final int[] LFH_SIGNATURE = { 0x50, 0x4b, 0x03, 0x04 }; 27 static final int[] ECD_SIGNATURE = { 0x50, 0x4b, 0x05, 0x06 }; 28 static final int[] DD_SIGNATURE = { 0x50, 0x4b, 0x07, 0x08 }; 29 30 static void updateKeys( byte charAt, int[] keys ) { 31 keys[0] = crc32( keys[0], charAt ); 32 keys[1] += keys[0] & 0xff; 33 keys[1] = keys[1] * 134775813 + 1; 34 keys[2] = crc32( keys[2], (byte) ( keys[1] >> 24 ) ); 35 } 36 37 static int crc32( int oldCrc, byte charAt ) { 38 return ( ( oldCrc >>> 8 ) ^ CRC_TABLE[( oldCrc ^ charAt ) & 0xff] ); 39 } 40 41 static enum State { 42 SIGNATURE, FLAGS, COMPRESSED_SIZE, FN_LENGTH, EF_LENGTH, HEADER, DATA, TAIL, CRC 43 } 44 45 static enum Section { 46 FILE_HEADER, FILE_DATA, DATA_DESCRIPTOR 47 } 48 }
|