Skip to content

Commit b69843a

Browse files
#282 Convert password to utf-8 before encrypting
1 parent d568afc commit b69843a

5 files changed

Lines changed: 118 additions & 98 deletions

File tree

src/main/java/net/lingala/zip4j/crypto/AESEncrypter.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323

2424
import java.security.SecureRandom;
2525

26+
import static net.lingala.zip4j.crypto.AesCipherUtil.derivePasswordBasedKey;
27+
import static net.lingala.zip4j.crypto.AesCipherUtil.derivePasswordVerifier;
28+
import static net.lingala.zip4j.crypto.AesCipherUtil.getAESEngine;
29+
import static net.lingala.zip4j.crypto.AesCipherUtil.getMacBasedPRF;
2630
import static net.lingala.zip4j.crypto.AesCipherUtil.prepareBuffAESIVBytes;
2731
import static net.lingala.zip4j.util.InternalZipConstants.AES_BLOCK_SIZE;
2832

@@ -31,19 +35,17 @@
3135
*/
3236
public class AESEncrypter implements Encrypter {
3337

34-
private char[] password;
35-
private AesKeyStrength aesKeyStrength;
3638
private AESEngine aesEngine;
3739
private MacBasedPRF mac;
38-
private SecureRandom random = new SecureRandom();
40+
private final SecureRandom random = new SecureRandom();
3941

4042
private boolean finished;
4143

4244
private int nonce = 1;
4345
private int loopCount = 0;
4446

45-
private byte[] iv;
46-
private byte[] counterBlock;
47+
private final byte[] iv;
48+
private final byte[] counterBlock;
4749
private byte[] derivedPasswordVerifier;
4850
private byte[] saltBytes;
4951

@@ -56,20 +58,18 @@ public AESEncrypter(char[] password, AesKeyStrength aesKeyStrength) throws ZipEx
5658
throw new ZipException("Invalid AES key strength");
5759
}
5860

59-
this.password = password;
60-
this.aesKeyStrength = aesKeyStrength;
6161
this.finished = false;
6262
counterBlock = new byte[AES_BLOCK_SIZE];
6363
iv = new byte[AES_BLOCK_SIZE];
64-
init();
64+
init(password, aesKeyStrength);
6565
}
6666

67-
private void init() throws ZipException {
67+
private void init(char[] password, AesKeyStrength aesKeyStrength) throws ZipException {
6868
saltBytes = generateSalt(aesKeyStrength.getSaltLength());
69-
byte[] derivedKey = AesCipherUtil.derivePasswordBasedKey(saltBytes, password, aesKeyStrength);
70-
derivedPasswordVerifier = AesCipherUtil.derivePasswordVerifier(derivedKey, aesKeyStrength);
71-
aesEngine = AesCipherUtil.getAESEngine(derivedKey, aesKeyStrength);
72-
mac = AesCipherUtil.getMacBasedPRF(derivedKey, aesKeyStrength);
69+
byte[] derivedKey = derivePasswordBasedKey(saltBytes, password, aesKeyStrength);
70+
derivedPasswordVerifier = derivePasswordVerifier(derivedKey, aesKeyStrength);
71+
aesEngine = getAESEngine(derivedKey, aesKeyStrength);
72+
mac = getMacBasedPRF(derivedKey, aesKeyStrength);
7373
}
7474

7575
public int encryptData(byte[] buff) throws ZipException {
@@ -116,18 +116,18 @@ private byte[] generateSalt(int size) throws ZipException {
116116
throw new ZipException("invalid salt size, cannot generate salt");
117117
}
118118

119-
int rounds = 0;
119+
int rounds;
120120

121121
if (size == 8) {
122122
rounds = 2;
123-
} else if (size == 16) {
123+
} else {
124124
rounds = 4;
125125
}
126126

127127
byte[] salt = new byte[size];
128128
for (int j = 0; j < rounds; j++) {
129129
int i = random.nextInt();
130-
salt[0 + j * 4] = (byte) (i >> 24);
130+
salt[j * 4] = (byte) (i >> 24);
131131
salt[1 + j * 4] = (byte) (i >> 16);
132132
salt[2 + j * 4] = (byte) (i >> 8);
133133
salt[3 + j * 4] = (byte) i;

src/main/java/net/lingala/zip4j/crypto/StandardEncrypter.java

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,10 @@
2525

2626
public class StandardEncrypter implements Encrypter {
2727

28-
private ZipCryptoEngine zipCryptoEngine;
28+
private final ZipCryptoEngine zipCryptoEngine = new ZipCryptoEngine();
2929
private byte[] headerBytes;
3030

3131
public StandardEncrypter(char[] password, long key) throws ZipException {
32-
this.zipCryptoEngine = new ZipCryptoEngine();
33-
34-
this.headerBytes = new byte[STD_DEC_HDR_SIZE];
3532
init(password, key);
3633
}
3734

@@ -40,17 +37,13 @@ private void init(char[] password, long key) throws ZipException {
4037
throw new ZipException("input password is null or empty, cannot initialize standard encrypter");
4138
}
4239
zipCryptoEngine.initKeys(password);
43-
headerBytes = generateRandomBytes(STD_DEC_HDR_SIZE);
40+
headerBytes = generateRandomBytes();
4441
// Initialize again since the generated bytes were encrypted.
4542
zipCryptoEngine.initKeys(password);
4643

4744
headerBytes[STD_DEC_HDR_SIZE - 1] = (byte) ((key >>> 24));
4845
headerBytes[STD_DEC_HDR_SIZE - 2] = (byte) ((key >>> 16));
4946

50-
if (headerBytes.length < STD_DEC_HDR_SIZE) {
51-
throw new ZipException("invalid header bytes generated, cannot perform standard encryption");
52-
}
53-
5447
encryptData(headerBytes);
5548
}
5649

@@ -78,17 +71,12 @@ protected byte encryptByte(byte val) {
7871
return temp_val;
7972
}
8073

81-
protected byte[] generateRandomBytes(int size) throws ZipException {
82-
if (size <= 0) {
83-
throw new ZipException("size is either 0 or less than 0, cannot generate header for standard encryptor");
84-
}
85-
86-
byte[] buff = new byte[size];
74+
protected byte[] generateRandomBytes() {
75+
byte[] buff = new byte[STD_DEC_HDR_SIZE];
8776
SecureRandom random = new SecureRandom();
8877
for (int i = 0; i < buff.length; i++) {
8978
buff[i] = encryptByte((byte) random.nextInt(256));
9079
}
91-
9280
return buff;
9381
}
9482

Lines changed: 65 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,65 @@
1-
/*
2-
* Copyright 2010 Srikanth Reddy Lingala
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing,
11-
* software distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
16-
17-
package net.lingala.zip4j.crypto.engine;
18-
19-
public class ZipCryptoEngine {
20-
21-
private final int keys[] = new int[3];
22-
private static final int[] CRC_TABLE = new int[256];
23-
24-
static {
25-
for (int i = 0; i < 256; i++) {
26-
int r = i;
27-
for (int j = 0; j < 8; j++) {
28-
if ((r & 1) == 1) {
29-
r = (r >>> 1) ^ 0xedb88320;
30-
} else {
31-
r >>>= 1;
32-
}
33-
}
34-
CRC_TABLE[i] = r;
35-
}
36-
}
37-
38-
public void initKeys(char[] password) {
39-
keys[0] = 305419896;
40-
keys[1] = 591751049;
41-
keys[2] = 878082192;
42-
for (int i = 0; i < password.length; i++) {
43-
updateKeys((byte) (password[i] & 0xff));
44-
}
45-
}
46-
47-
public void updateKeys(byte charAt) {
48-
keys[0] = crc32(keys[0], charAt);
49-
keys[1] += keys[0] & 0xff;
50-
keys[1] = keys[1] * 134775813 + 1;
51-
keys[2] = crc32(keys[2], (byte) (keys[1] >> 24));
52-
}
53-
54-
private int crc32(int oldCrc, byte charAt) {
55-
return ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff]);
56-
}
57-
58-
public byte decryptByte() {
59-
int temp = keys[2] | 2;
60-
return (byte) ((temp * (temp ^ 1)) >>> 8);
61-
}
62-
}
1+
/*
2+
* Copyright 2010 Srikanth Reddy Lingala
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.lingala.zip4j.crypto.engine;
18+
19+
import static net.lingala.zip4j.util.Zip4jUtil.convertCharArrayToByteArray;
20+
21+
public class ZipCryptoEngine {
22+
23+
private final int[] keys = new int[3];
24+
private static final int[] CRC_TABLE = new int[256];
25+
26+
static {
27+
for (int i = 0; i < 256; i++) {
28+
int r = i;
29+
for (int j = 0; j < 8; j++) {
30+
if ((r & 1) == 1) {
31+
r = (r >>> 1) ^ 0xedb88320;
32+
} else {
33+
r >>>= 1;
34+
}
35+
}
36+
CRC_TABLE[i] = r;
37+
}
38+
}
39+
40+
public void initKeys(char[] password) {
41+
keys[0] = 305419896;
42+
keys[1] = 591751049;
43+
keys[2] = 878082192;
44+
byte[] bytes = convertCharArrayToByteArray(password);
45+
for (byte b : bytes) {
46+
updateKeys((byte) (b & 0xff));
47+
}
48+
}
49+
50+
public void updateKeys(byte charAt) {
51+
keys[0] = crc32(keys[0], charAt);
52+
keys[1] += keys[0] & 0xff;
53+
keys[1] = keys[1] * 134775813 + 1;
54+
keys[2] = crc32(keys[2], (byte) (keys[1] >> 24));
55+
}
56+
57+
private int crc32(int oldCrc, byte charAt) {
58+
return ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff]);
59+
}
60+
61+
public byte decryptByte() {
62+
int temp = keys[2] | 2;
63+
return (byte) ((temp * (temp ^ 1)) >>> 8);
64+
}
65+
}

src/main/java/net/lingala/zip4j/util/Zip4jUtil.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
import java.io.File;
2424
import java.io.IOException;
2525
import java.io.InputStream;
26+
import java.nio.ByteBuffer;
27+
import java.nio.CharBuffer;
28+
import java.nio.charset.StandardCharsets;
2629
import java.util.Calendar;
2730

2831
public class Zip4jUtil {
@@ -96,11 +99,18 @@ private static long dosToEpochTime(long dosTime) {
9699
}
97100

98101
public static byte[] convertCharArrayToByteArray(char[] charArray) {
99-
byte[] bytes = new byte[charArray.length];
100-
for (int i = 0; i < charArray.length; i++) {
101-
bytes[i] = (byte) charArray[i];
102+
try {
103+
ByteBuffer buf = StandardCharsets.UTF_8.encode(CharBuffer.wrap(charArray));
104+
byte[] bytes = new byte[buf.limit()];
105+
buf.get(bytes);
106+
return bytes;
107+
} catch (Exception e) {
108+
byte[] bytes = new byte[charArray.length];
109+
for (int i = 0; i < charArray.length; i++) {
110+
bytes[i] = (byte) charArray[i];
111+
}
112+
return bytes;
102113
}
103-
return bytes;
104114
}
105115

106116
public static CompressionMethod getCompressionMethod(LocalFileHeader localFileHeader) {

src/test/java/net/lingala/zip4j/util/Zip4jUtilTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,25 @@ public void testConvertCharArrayToByteArray() {
127127
assertThat(byteArray[8]).isEqualTo((byte)'y');
128128
}
129129

130+
@Test
131+
public void testConvertCharArrayToByteArrayChineseChars() {
132+
char[] charArray = "你好".toCharArray();
133+
134+
byte[] byteArray = Zip4jUtil.convertCharArrayToByteArray(charArray);
135+
136+
try {
137+
// Make sure that StandardCharsets exists on the classpath
138+
Class.forName("java.nio.charset.StandardCharsets");
139+
assertThat(byteArray.length).isEqualTo(6);
140+
assertThat(byteArray).isEqualTo(new byte[]{-28, -67, -96, -27, -91, -67});
141+
} catch (ClassNotFoundException e) {
142+
// In some test environments (old Android SDK), StandardCharset class does not exist, in this case
143+
// the method under test falls back to converting char to its byte representation
144+
assertThat(byteArray.length).isEqualTo(2);
145+
assertThat(byteArray).isEqualTo(new byte[]{96, 125});
146+
}
147+
}
148+
130149
@Test
131150
public void testGetCompressionMethodForNonAesReturnsAsIs() {
132151
LocalFileHeader localFileHeader = new LocalFileHeader();

0 commit comments

Comments
 (0)