Skip to content

Commit

Permalink
#103 Avoid array when ECD
Browse files Browse the repository at this point in the history
  • Loading branch information
oleg-cherednik committed Nov 21, 2024
1 parent 893c576 commit b18c935
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 54 deletions.
18 changes: 16 additions & 2 deletions src/main/java/ru/olegcherednik/zip4jvm/crypto/aes/AesDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,23 @@ public final class AesDecoder implements Decoder {
@Getter
private final long compressedSize;

public static AesDecoder create(ZipEntry zipEntry, DataInput in) {
@SuppressWarnings("NewMethodNamingConvention")
public static AesDecoder create128(ZipEntry zipEntry, DataInput in) {
return create(zipEntry, AesStrength.S128, in);
}

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

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

private static AesDecoder create(ZipEntry zipEntry, AesStrength strength, 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);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package ru.olegcherednik.zip4jvm.crypto.strong.cd;
package ru.olegcherednik.zip4jvm.crypto.aes;

import ru.olegcherednik.zip4jvm.crypto.Decoder;
import ru.olegcherednik.zip4jvm.crypto.aes.AesStrength;
import ru.olegcherednik.zip4jvm.crypto.strong.DecryptionHeader;
import ru.olegcherednik.zip4jvm.exception.IncorrectCentralDirectoryPasswordException;
import ru.olegcherednik.zip4jvm.io.ByteOrder;
import ru.olegcherednik.zip4jvm.utils.quitely.Quietly;

Expand All @@ -20,7 +18,7 @@
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class AesEcdDecoder implements Decoder {

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

Expand Down Expand Up @@ -54,16 +52,8 @@ private static AesEcdDecoder create(DecryptionHeader decryptionHeader,
long compressedSize,
ByteOrder byteOrder) {
return Quietly.doQuietly(() -> {
Cipher cipher = AesEcdEngine.createCipher(decryptionHeader, password, strength);
byte[] passwordValidationData = cipher.update(decryptionHeader.getPasswordValidationData());

long actual = DecryptionHeader.getActualCrc32(passwordValidationData);
long expected = DecryptionHeader.getExpectedCrc32(passwordValidationData, byteOrder);

if (expected != actual)
throw new IncorrectCentralDirectoryPasswordException();

AesEcdEngine engine = new AesEcdEngine(cipher);
Cipher cipher = AesStrongEngine.createCipher(decryptionHeader, password, strength, byteOrder);
AesStrongEngine engine = new AesStrongEngine(cipher);
return new AesEcdDecoder(engine, compressedSize);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import ru.olegcherednik.zip4jvm.crypto.Decoder;
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.DataInput;
import ru.olegcherednik.zip4jvm.io.readers.crypto.strong.DecryptionHeaderReader;
Expand Down Expand Up @@ -48,30 +49,43 @@ public final class AesStrongDecoder implements Decoder {

private long decryptedBytes;

public static AesStrongDecoder create(ZipEntry zipEntry, DataInput in) {
@SuppressWarnings("NewMethodNamingConvention")
public static AesStrongDecoder create128(ZipEntry zipEntry, DataInput in) {
return create(zipEntry, AesStrength.S128, in);
}

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

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

public static AesStrongDecoder create(ZipEntry zipEntry, AesStrength strength, DataInput in) {
return Quietly.doQuietly(() -> {
in.mark(DECRYPTION_HEADER);
Cipher cipher = createCipher(zipEntry, in);
Cipher cipher = createCipher(zipEntry, strength, 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 {
private static Cipher createCipher(ZipEntry zipEntry, AesStrength strength, DataInput in) throws IOException {
// TODO should check that decryptionHeader has same strength
assert strength != null;

DecryptionHeader decryptionHeader = new DecryptionHeaderReader().read(in);
EncryptionAlgorithm encryptionAlgorithm = decryptionHeader.getEncryptionAlgorithm();
Cipher cipher = encryptionAlgorithm.createCipher(decryptionHeader, zipEntry.getPassword());

byte[] passwordValidationData = cipher.update(decryptionHeader.getPasswordValidationData());

long actual = DecryptionHeader.getActualCrc32(passwordValidationData);
long expected = DecryptionHeader.getExpectedCrc32(passwordValidationData, in.getByteOrder());

if (expected != actual)
try {
return encryptionAlgorithm.createCipher(decryptionHeader, zipEntry.getPassword(), in.getByteOrder());
} catch (IncorrectPasswordException e) {
throw new IncorrectZipEntryPasswordException(zipEntry.getFileName());

return cipher;
}
}

// ---------- Decoder ----------
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package ru.olegcherednik.zip4jvm.crypto.strong.cd;
package ru.olegcherednik.zip4jvm.crypto.aes;

import ru.olegcherednik.zip4jvm.crypto.Engine;
import ru.olegcherednik.zip4jvm.crypto.aes.AesStrength;
import ru.olegcherednik.zip4jvm.crypto.strong.DecryptionHeader;
import ru.olegcherednik.zip4jvm.exception.IncorrectPasswordException;
import ru.olegcherednik.zip4jvm.io.ByteOrder;
import ru.olegcherednik.zip4jvm.utils.quitely.Quietly;

import lombok.AccessLevel;
Expand All @@ -23,7 +23,7 @@
* @since 21.11.2024
*/
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public final class AesEcdEngine implements Engine {
public final class AesStrongEngine implements Engine {

private final Cipher cipher;

Expand All @@ -47,19 +47,25 @@ public byte encrypt(byte b) {

// ---------- static

public static Cipher createCipher128(DecryptionHeader decryptionHeader, char[] password) {
return createCipher(decryptionHeader, password, AesStrength.S128);
@SuppressWarnings("NewMethodNamingConvention")
public static Cipher createCipher128(DecryptionHeader decryptionHeader, char[] password, ByteOrder byteOrder) {
return createCipher(decryptionHeader, password, AesStrength.S128, byteOrder);
}

public static Cipher createCipher192(DecryptionHeader decryptionHeader, char[] password) {
return createCipher(decryptionHeader, password, AesStrength.S192);
@SuppressWarnings("NewMethodNamingConvention")
public static Cipher createCipher192(DecryptionHeader decryptionHeader, char[] password, ByteOrder byteOrder) {
return createCipher(decryptionHeader, password, AesStrength.S192, byteOrder);
}

public static Cipher createCipher256(DecryptionHeader decryptionHeader, char[] password) {
return createCipher(decryptionHeader, password, AesStrength.S256);
@SuppressWarnings("NewMethodNamingConvention")
public static Cipher createCipher256(DecryptionHeader decryptionHeader, char[] password, ByteOrder byteOrder) {
return createCipher(decryptionHeader, password, AesStrength.S256, byteOrder);
}

public static Cipher createCipher(DecryptionHeader decryptionHeader, char[] password, AesStrength strength) {
public static Cipher createCipher(DecryptionHeader decryptionHeader,
char[] password,
AesStrength strength,
ByteOrder byteOrder) {
return Quietly.doQuietly(() -> {
IvParameterSpec iv = new IvParameterSpec(decryptionHeader.getIv());
byte[] randomData = decryptRandomData(decryptionHeader, password, strength, iv);
Expand All @@ -69,6 +75,14 @@ public static Cipher createCipher(DecryptionHeader decryptionHeader, char[] pass
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, iv);

byte[] passwordValidationData = cipher.update(decryptionHeader.getPasswordValidationData());

long actual = DecryptionHeader.getActualCrc32(passwordValidationData);
long expected = DecryptionHeader.getExpectedCrc32(passwordValidationData, byteOrder);

if (expected != actual)
throw new IncorrectPasswordException();

return cipher;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
package ru.olegcherednik.zip4jvm.crypto.strong;

import ru.olegcherednik.zip4jvm.crypto.Decoder;
import ru.olegcherednik.zip4jvm.crypto.strong.cd.AesEcdDecoder;
import ru.olegcherednik.zip4jvm.crypto.strong.cd.AesEcdEngine;
import ru.olegcherednik.zip4jvm.crypto.aes.AesEcdDecoder;
import ru.olegcherednik.zip4jvm.crypto.aes.AesStrongEngine;
import ru.olegcherednik.zip4jvm.exception.EncryptionNotSupportedException;
import ru.olegcherednik.zip4jvm.io.ByteOrder;
import ru.olegcherednik.zip4jvm.model.EncryptionMethod;
Expand All @@ -47,17 +47,17 @@ public enum EncryptionAlgorithm {
AES_128(0x660E,
EncryptionMethod.AES_STRONG_128,
AesEcdDecoder::create128,
AesEcdEngine::createCipher128,
AesStrongEngine::createCipher128,
"AES-128"),
AES_192(0x660F,
EncryptionMethod.AES_STRONG_192,
AesEcdDecoder::create192,
AesEcdEngine::createCipher192,
AesStrongEngine::createCipher192,
"AES-192"),
AES_256(0x6610,
EncryptionMethod.AES_STRONG_256,
AesEcdDecoder::create256,
AesEcdEngine::createCipher256,
AesStrongEngine::createCipher256,
"AES-256"),
RC2(0x6702, EncryptionMethod.RC2, null, null, "RC2"),
RC4(0x6801, EncryptionMethod.RC4, null, null, "RC4"),
Expand All @@ -82,10 +82,10 @@ public Decoder createEcdDecoder(DecryptionHeader decryptionHeader,
.create(decryptionHeader, password, compressedSize, byteOrder);
}

public Cipher createCipher(DecryptionHeader decryptionHeader, char[] password) {
public Cipher createCipher(DecryptionHeader decryptionHeader, char[] password, ByteOrder byteOrder) {
return Optional.ofNullable(cipherFactory)
.orElseThrow(() -> new EncryptionNotSupportedException(this))
.create(decryptionHeader, password);
.create(decryptionHeader, password, byteOrder);
}

public static EncryptionAlgorithm parseCode(int code) {
Expand All @@ -104,7 +104,7 @@ private interface DecoderFactory {

private interface CipherFactory {

Cipher create(DecryptionHeader decryptionHeader, char[] password);
Cipher create(DecryptionHeader decryptionHeader, char[] password, ByteOrder byteOrder);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ public enum EncryptionMethod {

OFF(zipEntry -> Encoder.NULL, (zipEntry, in) -> Decoder.NULL, ZipEntry::getChecksum),
PKWARE(PkwareEncoder::create, PkwareDecoder::create, ZipEntry::getChecksum),
AES_128(AesEncoder::create, AesDecoder::create, AesEngine::getChecksum),
AES_192(AesEncoder::create, AesDecoder::create, AesEngine::getChecksum),
AES_256(AesEncoder::create, AesDecoder::create, AesEngine::getChecksum),
AES_STRONG_128(null, AesStrongDecoder::create, AesEngine::getChecksum),
AES_STRONG_192(null, AesStrongDecoder::create, AesEngine::getChecksum),
AES_STRONG_256(null, AesStrongDecoder::create, AesEngine::getChecksum),
AES_128(AesEncoder::create, AesDecoder::create128, AesEngine::getChecksum),
AES_192(AesEncoder::create, AesDecoder::create192, AesEngine::getChecksum),
AES_256(AesEncoder::create, AesDecoder::create256, AesEngine::getChecksum),
AES_STRONG_128(null, AesStrongDecoder::create128, AesEngine::getChecksum),
AES_STRONG_192(null, AesStrongDecoder::create192, AesEngine::getChecksum),
AES_STRONG_256(null, AesStrongDecoder::create256, AesEngine::getChecksum),
DES(null, null, ZipEntry::getChecksum),
RC2_PRE_52(null, null, ZipEntry::getChecksum),
TRIPLE_DES_168(null, null, ZipEntry::getChecksum),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import ru.olegcherednik.zip4jvm.utils.ReflectionUtils;

import org.apache.commons.lang3.ArrayUtils;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.IOException;
Expand All @@ -44,10 +45,12 @@
@Test
public class AesDecoderTest {

public void shouldThrowZip4jvmExceptionWhenCreateAndException() throws IOException {
public void shouldThrowZip4jvmExceptionWhenCreateAndException(AesStrength strength) throws IOException {
try (DataInput in = mock(DataInput.class)) {
ZipEntry entry = mock(ZipEntry.class);
assertThatThrownBy(() -> AesDecoder.create(entry, in)).isExactlyInstanceOf(Zip4jvmException.class);
assertThatThrownBy(() -> AesDecoder.create128(entry, in)).isExactlyInstanceOf(Zip4jvmException.class);
assertThatThrownBy(() -> AesDecoder.create192(entry, in)).isExactlyInstanceOf(Zip4jvmException.class);
assertThatThrownBy(() -> AesDecoder.create256(entry, in)).isExactlyInstanceOf(Zip4jvmException.class);
}
}

Expand Down

0 comments on commit b18c935

Please sign in to comment.