srikanth-lingala / zip4j

A Java library for zip files and streams
Apache License 2.0
2k stars 307 forks source link

New Features Request: Supports AES-192 encryption features #536

Open wangbinhang opened 5 months ago

wangbinhang commented 5 months ago

For some historical reasons I need to take care of the AES-192 encryption method, I noticed that KEY_STRENGTH_192 has been defined in the AesKeyStrength enum class, but there is no compatibility treatment in AESEncrypter, can I know why? Also I have modified the AESEncrypter class myself, I have bolded the changed code, can you help me review if there are any other issues? Many thanks.

zip4j version:2.11.5

Source Code

/*

package net.lingala.zip4j.crypto;

import net.lingala.zip4j.crypto.PBKDF2.MacBasedPRF; import net.lingala.zip4j.crypto.engine.AESEngine; import net.lingala.zip4j.exception.ZipException; import net.lingala.zip4j.model.enums.AesKeyStrength;

import java.security.SecureRandom;

import static net.lingala.zip4j.crypto.AesCipherUtil.derivePasswordBasedKey; import static net.lingala.zip4j.crypto.AesCipherUtil.derivePasswordVerifier; import static net.lingala.zip4j.crypto.AesCipherUtil.getAESEngine; import static net.lingala.zip4j.crypto.AesCipherUtil.getMacBasedPRF; import static net.lingala.zip4j.crypto.AesCipherUtil.prepareBuffAESIVBytes; import static net.lingala.zip4j.util.InternalZipConstants.AES_BLOCK_SIZE;

/**

public AESEncrypter(char[] password, AesKeyStrength aesKeyStrength, boolean useUtf8ForPassword) throws ZipException { if (password == null || password.length == 0) { throw new ZipException("input password is empty or null"); } if (aesKeyStrength != AesKeyStrength.KEY_STRENGTH_128 && aesKeyStrength != AesKeyStrength.KEY_STRENGTH_192 && aesKeyStrength != AesKeyStrength.KEY_STRENGTH_256) { throw new ZipException("Invalid AES key strength"); }

this.finished = false;
counterBlock = new byte[AES_BLOCK_SIZE];
iv = new byte[AES_BLOCK_SIZE];
init(password, aesKeyStrength, useUtf8ForPassword);

}

private void init(char[] password, AesKeyStrength aesKeyStrength, boolean useUtf8ForPassword) throws ZipException { saltBytes = generateSalt(aesKeyStrength.getSaltLength()); byte[] derivedKey = derivePasswordBasedKey(saltBytes, password, aesKeyStrength, useUtf8ForPassword); derivedPasswordVerifier = derivePasswordVerifier(derivedKey, aesKeyStrength); aesEngine = getAESEngine(derivedKey, aesKeyStrength); mac = getMacBasedPRF(derivedKey, aesKeyStrength); }

public int encryptData(byte[] buff) throws ZipException { if (buff == null) { throw new ZipException("input bytes are null, cannot perform AES encryption"); } return encryptData(buff, 0, buff.length); }

public int encryptData(byte[] buff, int start, int len) throws ZipException {

if (finished) {
  // A non 16 byte block has already been passed to encrypter
  // non 16 byte block should be the last block of compressed data in AES encryption
  // any more encryption will lead to corruption of data
  throw new ZipException("AES Encrypter is in finished state (A non 16 byte block has already been passed to encrypter)");
}

if (len % 16 != 0) {
  this.finished = true;
}

for (int j = start; j < (start + len); j += AES_BLOCK_SIZE) {
  loopCount = (j + AES_BLOCK_SIZE <= (start + len)) ?
      AES_BLOCK_SIZE : ((start + len) - j);

  prepareBuffAESIVBytes(iv, nonce);
  aesEngine.processBlock(iv, counterBlock);

  for (int k = 0; k < loopCount; k++) {
    buff[j + k] = (byte) (buff[j + k] ^ counterBlock[k]);
  }

  mac.update(buff, j, loopCount);
  nonce++;
}

return len;

}

private byte[] generateSalt(int size) throws ZipException {

if (size != 8 && size != 12 && size != 16) {
  throw new ZipException("invalid salt size, cannot generate salt");
}

int rounds;

if (size == 8) {
  rounds = 2;
} **else if (size == 12) {
  rounds = 3;
}** else {
  rounds = 4;
}

byte[] salt = new byte[size];
for (int j = 0; j < rounds; j++) {
  int i = random.nextInt();
  salt[j * 4] = (byte) (i >> 24);
  salt[1 + j * 4] = (byte) (i >> 16);
  salt[2 + j * 4] = (byte) (i >> 8);
  salt[3 + j * 4] = (byte) i;
}
return salt;

}

public byte[] getFinalMac() { byte[] rawMacBytes = mac.doFinal(); byte[] macBytes = new byte[10]; System.arraycopy(rawMacBytes, 0, macBytes, 0, 10); return macBytes; }

public byte[] getDerivedPasswordVerifier() { return derivedPasswordVerifier; }

public byte[] getSaltBytes() { return saltBytes; } }