网上找到的资料,还没试过,谁要是试了回复下吧。
原文出自:http://blog.alutam.com/2009/10/31/reading-password-protected-zip-files-in-java/
On a recent “fun” project, I needed my application to be able to access password-protected zip files of a particular format. It was one of these features I thought will take me no time to implement. Anyway, to my surprise, neither JDK supports password-protected
ZIP files, nor I was able to find a suitable Java open source library I could use for that purpose. So, I ended up writing the utility class on my own. I wrote an implementation of
java.io.InputStream that filters the ZIP file data and turns a password-protected ZIP into an unprotected one on the fly – so the stream can be nicely chained with
java.util.zip.ZipInputStream. Although the class is specifically targeted at the particular type of ZIP files I had to deal with (see the limitations below), maybe other people have to deal with the same type of files, or this class can provide
a good start for others to turn it into a utility that would work with any type of ZIP (maybe I will do it myself some day – for now I don’t have time).
To implement this class I used the
ZIP File Format Specification as the source of information. I also used the
7-zip project (C++) as a reference during the debugging to verify my understanding of the ZIP spec. and the CRC algorithm.
So, here is the class:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import
java.io.IOException;
import
java.io.InputStream;
public
class ZipDecryptInputStream extends
InputStream {
private
static final
int[] CRC_TABLE = new
int[256];
// compute the table
// (could also have it pre-computed - see
http://snippets.dzone.com/tag/crc32)
static
{
for
(int
i = 0; i <
256; i++) {
int
r = i;
for
(int
j = 0; j <
8; j++) {
if
((r & 1) ==
1) {
r = (r >>>
1) ^ 0xedb88320;
}
else {
r >>>=
1;
}
}
CRC_TABLE[i] = r;
}
}
private
static final
int DECRYPT_HEADER_SIZE = 12;
private
static final
int[] LFH_SIGNATURE = {0x50,
0x4b, 0x03,
0x04};
private
final InputStream delegate;
private
final String password;
private
final int
keys[] = new
int[3];
private
State state = State.SIGNATURE;
private
int skipBytes;
private
int compressedSize;
private
int value;
private
int valuePos;
private
int valueInc;
public
ZipDecryptInputStream(InputStream stream, String password) {
this.delegate = stream;
this.password = password;
}
@Override
public
int read() throws
IOException {
int
result = delegate.read();
if
(skipBytes == 0) {
switch
(state) {
case
SIGNATURE:
if
(result != LFH_SIGNATURE[valuePos]) {
state = State.TAIL;
}
else {
valuePos++;
if
(valuePos >= LFH_SIGNATURE.length) {
skipBytes =
2;
state = State.FLAGS;
}
}
break;
case
FLAGS:
if
((result & 1) ==
0) {
throw
new IllegalStateException("ZIP not password protected.");
}
if
((result & 64) ==
64) {
throw
new IllegalStateException("Strong encryption used.");
}
if
((result & 8) ==
8) {
throw
new IllegalStateException("Unsupported ZIP format.");
}
result -=
1;
compressedSize =
0;
valuePos =
0;
valueInc = DECRYPT_HEADER_SIZE;
state = State.COMPRESSED_SIZE;
skipBytes =
11;
break;
case
COMPRESSED_SIZE:
compressedSize += result << (8
-
valuePos);
result -= valueInc;
if
(result < 0) {
valueInc =
1;
result +=
256;
}
else {
valueInc =
0;
}
valuePos++;
if
(valuePos > 3) {
valuePos =
0;
value =
0;
state = State.FN_LENGTH;
skipBytes =
4;
}
break;
case
FN_LENGTH:
case
EF_LENGTH:
value += result <<
8 * valuePos;
if
(valuePos == 1) {
valuePos =
0;
if
(state == State.FN_LENGTH) {
state = State.EF_LENGTH;
}
else {
state = State.HEADER;
skipBytes = value;
}
}
else {
valuePos =
1;
}
break;
case
HEADER:
initKeys(password);
for
(int
i = 0; i < DECRYPT_HEADER_SIZE; i++) {
updateKeys((byte) (result ^ decryptByte()));
result = delegate.read();
}
compressedSize -= DECRYPT_HEADER_SIZE;
state = State.DATA;
// intentionally no break
case
DATA:
result = (result ^ decryptByte()) &
0xff;
updateKeys((byte) result);
compressedSize--;
if
(compressedSize == 0) {
valuePos =
0;
state = State.SIGNATURE;
}
break;
case
TAIL:
// do nothing
}
}
else {
skipBytes--;
}
return
result;
}@Override
public
void close() throws
IOException {
delegate.close();
super.close();
}private
void initKeys(String password) {
keys[0] =
305419896;
keys[1] =
591751049;
keys[2] =
878082192;
for
(int
i = 0; i < password.length(); i++) {
updateKeys((byte) (password.charAt(i) &
0xff));
}
}private
void updateKeys(byte
charAt) {
keys[0] = crc32(keys[0],
charAt);
keys[1] += keys[0] &
0xff;
keys[1] = keys[1] *
134775813 +
1;
keys[2] = crc32(keys[2],
(byte) (keys[1] >>
24));
}private
byte decryptByte() {
int
temp = keys[2] |
2;
return
(byte) ((temp * (temp ^
1)) >>> 8);
}private
int crc32(int
oldCrc, byte
charAt) {
return
((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) &
0xff]);
}private
static enum
State {
SIGNATURE, FLAGS, COMPRESSED_SIZE, FN_LENGTH, EF_LENGTH, HEADER, DATA, TAIL
}
}
These are the limitations:
Only the “Traditional PKWARE Encryption” is supported (spec. section VII)Files that have the “compressed length” information at the end of the data section (rather than at the beginning) are not supported (see “general purpose bit flag”, bit 3 in section V, subsection J in the spec.)
And this is how you can use it in your code:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.io.IOException;
import
java.util.zip.ZipEntry;
import
java.util.zip.ZipInputStream;
// usage: java Main [filename] [password]
public
class Main {
public
static void
main(String[] args) throws
IOException {
// password-protected zip file I need to read
FileInputStream fis =
new FileInputStream(args[0]);
// wrap it in the decrypt stream
ZipDecryptInputStream zdis =
new ZipDecryptInputStream(fis, args[1]);
// wrap the decrypt stream by the ZIP input stream
ZipInputStream zis =
new ZipInputStream(zdis);
// read all the zip entries and save them as files
ZipEntry ze;
while
((ze = zis.getNextEntry()) != null) {
FileOutputStream fos =
new FileOutputStream(ze.getName());
int
b;
while
((b = zis.read()) != -1) {
fos.write(b);
}
fos.close();
zis.closeEntry();
}
zis.close();
}
}
Previous Entry: Jersey Hands-On LabNext Entry: Jersey and Cross-Site Request Forgery (CSRF)