以下是源代碼,使用說明包含在文件頭部的注釋中。
1.
2.
3.
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 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 *


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 }
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 *


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

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[]

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[]

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 }
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 }