Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.11 #113

Merged
merged 26 commits into from
Dec 30, 2024
Merged

v1.11 #113

Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6987a3a
Update versions
oleg-cherednik Aug 26, 2023
3eefc7b
updates
oleg-cherednik Aug 30, 2023
95f5676
Update dependencies versions
oleg-cherednik Sep 29, 2024
c947f98
#83 GitHub Actions
oleg-cherednik Oct 6, 2024
79b1967
#86 Checkstyle
oleg-cherednik Oct 13, 2024
494272b
#79 Add encryption and compression settings for all entries
oleg-cherednik Oct 20, 2024
38fcb6a
#89 Add file or folder to specific path in zip
oleg-cherednik Oct 27, 2024
0f7eb09
Add image file content compare in tests
oleg-cherednik Oct 27, 2024
6643b00
#3 refactoring
oleg-cherednik Oct 29, 2024
c697a29
#3 store compression size optimization
oleg-cherednik Nov 1, 2024
cabc8d0
#93 Remove DataDescriptor supplier from settings
oleg-cherednik Nov 1, 2024
51649bf
Remove src.zip and gradle-ci.yml
oleg-cherednik Nov 1, 2024
2d0ff6c
DataInput hierarchy refactoring
oleg-cherednik Nov 2, 2024
5c4c91b
#96 DataOutput refactoring
oleg-cherednik Nov 6, 2024
816fa7e
Refactoring
oleg-cherednik Nov 9, 2024
1b0ad13
#99 Add AE-1 and AE-2 support
oleg-cherednik Nov 10, 2024
4ef1cb1
#101 datainput refactoring
oleg-cherednik Nov 20, 2024
6bf41a3
#103 Avoid using arrays when decrypt encrypted central directory
oleg-cherednik Nov 23, 2024
78c0780
Refactoring
oleg-cherednik Nov 24, 2024
22c2f8f
#106 Do not use CD when extract all entries
oleg-cherednik Dec 22, 2024
7898875
Refactoring
oleg-cherednik Dec 22, 2024
cc2e9b1
#107 Extract entries by prefix exclude prefix path
oleg-cherednik Dec 25, 2024
35a6f63
Refactoring
oleg-cherednik Dec 25, 2024
e6713a4
#20 Extract entries async
oleg-cherednik Dec 30, 2024
22b1fed
Update README.md
oleg-cherednik Dec 30, 2024
c6fcc88
Fix checkstyle
oleg-cherednik Dec 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
#103 Avoid using arrays when decrypt encrypted central directory
oleg-cherednik committed Dec 30, 2024
commit 6bf41a38fc1735ff80b3fc7d4cdbb5a19b47c075
2 changes: 1 addition & 1 deletion src/main/java/ru/olegcherednik/zip4jvm/crypto/Decoder.java
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
*/
package ru.olegcherednik.zip4jvm.crypto;

import ru.olegcherednik.zip4jvm.io.in.data.xxx.DataInput;
import ru.olegcherednik.zip4jvm.io.in.data.DataInput;

import java.io.IOException;

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package ru.olegcherednik.zip4jvm.crypto.aes;

import ru.olegcherednik.zip4jvm.crypto.Decoder;
import ru.olegcherednik.zip4jvm.crypto.strong.DecryptionHeader;
import ru.olegcherednik.zip4jvm.io.ByteOrder;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.io.IOException;
import javax.crypto.Cipher;

/**
* @author Oleg Cherednik
* @since 21.11.2024
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class AesCentralDirectoryDecoder implements Decoder {

private final AesStrongEngine engine;
@Getter
private final long compressedSize;

@SuppressWarnings("NewMethodNamingConvention")
public static AesCentralDirectoryDecoder create128(DecryptionHeader decryptionHeader,
char[] password,
long compressedSize,
ByteOrder byteOrder) throws IOException {
return create(decryptionHeader, password, AesStrength.S128, compressedSize, byteOrder);
}

@SuppressWarnings("NewMethodNamingConvention")
public static AesCentralDirectoryDecoder create192(DecryptionHeader decryptionHeader,
char[] password,
long compressedSize,
ByteOrder byteOrder) throws IOException {
return create(decryptionHeader, password, AesStrength.S192, compressedSize, byteOrder);
}

@SuppressWarnings("NewMethodNamingConvention")
public static AesCentralDirectoryDecoder create256(DecryptionHeader decryptionHeader,
char[] password,
long compressedSize,
ByteOrder byteOrder) throws IOException {
return create(decryptionHeader, password, AesStrength.S256, compressedSize, byteOrder);
}

private static AesCentralDirectoryDecoder create(DecryptionHeader decryptionHeader,
char[] password,
AesStrength strength,
long compressedSize,
ByteOrder byteOrder) throws IOException {
Cipher cipher = AesStrongEngine.createCipher(decryptionHeader, password, strength, byteOrder);
AesStrongEngine engine = new AesStrongEngine(cipher);
return new AesCentralDirectoryDecoder(engine, compressedSize);
}

// ---------- Decoder ----------

@Override
public int getBlockSize() {
return engine.getBlockSize();
}

// ---------- Decrypt ----------

@Override
public int decrypt(byte[] buf, int offs, int len) {
return engine.decrypt(buf, offs, len);
}

}
45 changes: 28 additions & 17 deletions src/main/java/ru/olegcherednik/zip4jvm/crypto/aes/AesDecoder.java
Original file line number Diff line number Diff line change
@@ -21,9 +21,8 @@
import ru.olegcherednik.zip4jvm.crypto.Decoder;
import ru.olegcherednik.zip4jvm.exception.IncorrectZipEntryPasswordException;
import ru.olegcherednik.zip4jvm.exception.Zip4jvmException;
import ru.olegcherednik.zip4jvm.io.in.data.xxx.DataInput;
import ru.olegcherednik.zip4jvm.io.in.data.DataInput;
import ru.olegcherednik.zip4jvm.model.entry.ZipEntry;
import ru.olegcherednik.zip4jvm.utils.quitely.Quietly;

import lombok.AccessLevel;
import lombok.Getter;
@@ -49,21 +48,33 @@ public final class AesDecoder implements Decoder {
@Getter
private final long compressedSize;

public static AesDecoder create(ZipEntry zipEntry, DataInput in) {
return Quietly.doQuietly(() -> {
AesStrength strength = AesEngine.getStrength(zipEntry.getEncryptionMethod());
byte[] salt = in.readBytes(strength.getSaltSize());
byte[] key = AesEngine.createKey(zipEntry.getPassword(), salt, strength);

Cipher cipher = AesEngine.createCipher(strength.createSecretKeyForCipher(key));
byte[] passwordChecksum = strength.createPasswordChecksum(key);
checkPasswordChecksum(passwordChecksum, zipEntry, in);

Mac mac = AesEngine.createMac(strength.createSecretKeyForMac(key));
AesEngine engine = new AesEngine(cipher, mac);
long compressedSize = AesEngine.getDataCompressedSize(zipEntry.getCompressedSize(), strength);
return new AesDecoder(engine, compressedSize);
});
@SuppressWarnings("NewMethodNamingConvention")
public static AesDecoder create128(ZipEntry zipEntry, DataInput in) throws IOException {
return create(zipEntry, AesStrength.S128, in);
}

@SuppressWarnings("NewMethodNamingConvention")
public static AesDecoder create192(ZipEntry zipEntry, DataInput in) throws IOException {
return create(zipEntry, AesStrength.S192, in);
}

@SuppressWarnings("NewMethodNamingConvention")
public static AesDecoder create256(ZipEntry zipEntry, DataInput in) throws IOException {
return create(zipEntry, AesStrength.S256, in);
}

private static AesDecoder create(ZipEntry zipEntry, AesStrength strength, DataInput in) throws IOException {
byte[] salt = in.readBytes(strength.getSaltSize());
byte[] key = AesEngine.createKey(zipEntry.getPassword(), salt, strength);

Cipher cipher = AesEngine.createCipher(strength.createSecretKeyForCipher(key));
byte[] passwordChecksum = strength.createPasswordChecksum(key);
checkPasswordChecksum(passwordChecksum, zipEntry, in);

Mac mac = AesEngine.createMac(strength.createSecretKeyForMac(key));
AesEngine engine = new AesEngine(cipher, mac);
long compressedSize = AesEngine.getDataCompressedSize(zipEntry.getCompressedSize(), strength);
return new AesDecoder(engine, compressedSize);
}

// ---------- Decrypt ----------
38 changes: 19 additions & 19 deletions src/main/java/ru/olegcherednik/zip4jvm/crypto/aes/AesEngine.java
Original file line number Diff line number Diff line change
@@ -27,13 +27,9 @@
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.PBEKeySpec;
@@ -121,25 +117,29 @@ public byte[] getMac() {
return mac.doFinal();
}

public static byte[] createKey(char[] password, byte[] salt, AesStrength strength)
throws NoSuchAlgorithmException, InvalidKeySpecException {
int keyLength = strength.getSize() * 2 + 16;
KeySpec keySpec = new PBEKeySpec(password, salt, ITERATION_COUNT, keyLength);
return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(keySpec).getEncoded();
public static byte[] createKey(char[] password, byte[] salt, AesStrength strength) {
return Quietly.doQuietly(() -> {
int keyLength = strength.getSize() * 2 + 16;
KeySpec keySpec = new PBEKeySpec(password, salt, ITERATION_COUNT, keyLength);
return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(keySpec).getEncoded();
});
}

public static Cipher createCipher(SecretKeySpec secretKeySpec)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
// use custom AES implementation, so no worry for DECRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher;
public static Cipher createCipher(SecretKeySpec secretKeySpec) {
return Quietly.doQuietly(() -> {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
// use custom AES implementation, so no worry for DECRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher;
});
}

public static Mac createMac(SecretKeySpec secretKeySpec) throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKeySpec);
return mac;
public static Mac createMac(SecretKeySpec secretKeySpec) {
return Quietly.doQuietly(() -> {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKeySpec);
return mac;
});
}

public static AesStrength getStrength(EncryptionMethod encryptionMethod) {
Original file line number Diff line number Diff line change
@@ -19,11 +19,11 @@
package ru.olegcherednik.zip4jvm.crypto.aes;

import ru.olegcherednik.zip4jvm.crypto.Decoder;
import ru.olegcherednik.zip4jvm.crypto.strong.AesCentralDirectoryCipherCreator;
import ru.olegcherednik.zip4jvm.crypto.strong.DecryptionHeader;
import ru.olegcherednik.zip4jvm.crypto.strong.EncryptionAlgorithm;
import ru.olegcherednik.zip4jvm.exception.IncorrectPasswordException;
import ru.olegcherednik.zip4jvm.exception.IncorrectZipEntryPasswordException;
import ru.olegcherednik.zip4jvm.io.in.data.xxx.DataInput;
import ru.olegcherednik.zip4jvm.io.in.data.DataInput;
import ru.olegcherednik.zip4jvm.io.readers.crypto.strong.DecryptionHeaderReader;
import ru.olegcherednik.zip4jvm.model.entry.ZipEntry;
import ru.olegcherednik.zip4jvm.utils.quitely.Quietly;
@@ -49,21 +49,20 @@ public final class AesStrongDecoder implements Decoder {

private long decryptedBytes;

public static AesStrongDecoder create(ZipEntry zipEntry, DataInput in) {
return Quietly.doQuietly(() -> {
in.mark(DECRYPTION_HEADER);
Cipher cipher = createCipher(zipEntry, in);
int decryptionHeaderSize = (int) in.getMarkSize(DECRYPTION_HEADER);
long compressedSize = zipEntry.getCompressedSize() - decryptionHeaderSize;
return new AesStrongDecoder(cipher, compressedSize);
});
public static AesStrongDecoder create(ZipEntry zipEntry, DataInput in) throws IOException {
in.mark(DECRYPTION_HEADER);
Cipher cipher = createCipher(zipEntry, in);
int decryptionHeaderSize = (int) in.getMarkSize(DECRYPTION_HEADER);
long compressedSize = zipEntry.getCompressedSize() - decryptionHeaderSize;
return new AesStrongDecoder(cipher, compressedSize);
}

private static Cipher createCipher(ZipEntry zipEntry, DataInput in) throws IOException {
DecryptionHeader decryptionHeader = new DecryptionHeaderReader().read(in);
EncryptionAlgorithm encryptionAlgorithm = decryptionHeader.getEncryptionAlgorithm();

try {
DecryptionHeader decryptionHeader = new DecryptionHeaderReader().read(in);
return new AesCentralDirectoryCipherCreator(zipEntry.getPassword())
.createCipher(in.getByteOrder(), decryptionHeader);
return encryptionAlgorithm.createCipher(decryptionHeader, zipEntry.getPassword(), in.getByteOrder());
} catch (IncorrectPasswordException e) {
throw new IncorrectZipEntryPasswordException(zipEntry.getFileName());
}
Loading