/*
* Copyright 2010 Srikanth Reddy Lingala
*
* Licensed 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 net.lingala.zip4j.crypto;
import java.util.Random;
import net.lingala.zip4j.crypto.PBKDF2.MacBasedPRF;
import net.lingala.zip4j.crypto.PBKDF2.PBKDF2Engine;
import net.lingala.zip4j.crypto.PBKDF2.PBKDF2Parameters;
import net.lingala.zip4j.crypto.engine.AESEngine;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.Raw;
import net.lingala.zip4j.util.Zip4jConstants;
public class AESEncrpyter implements IEncrypter {
private char[] password;
private int keyStrength;
private AESEngine aesEngine;
private MacBasedPRF mac;
private int KEY_LENGTH;
private int MAC_LENGTH;
private int SALT_LENGTH;
private final int PASSWORD_VERIFIER_LENGTH = 2;
private byte[] aesKey;
private byte[] macKey;
private byte[] derivedPasswordVerifier;
private byte[] saltBytes;
private boolean finished;
private int nonce = 1;
private int loopCount = 0;
private byte[] iv;
private byte[] counterBlock;
public AESEncrpyter(char[] password, int keyStrength) throws ZipException {
if (password == null || password.length == 0) {
throw new ZipException("input password is empty or null in AES encrypter constructor");
}
if (keyStrength != Zip4jConstants.AES_STRENGTH_128 &&
keyStrength != Zip4jConstants.AES_STRENGTH_256) {
throw new ZipException("Invalid key strength in AES encrypter constructor");
}
this.password = password;
this.keyStrength = keyStrength;
this.finished = false;
counterBlock = new byte[InternalZipConstants.AES_BLOCK_SIZE];
iv = new byte[InternalZipConstants.AES_BLOCK_SIZE];
init();
}
private void init() throws ZipException {
switch (keyStrength) {
case Zip4jConstants.AES_STRENGTH_128:
KEY_LENGTH = 16;
MAC_LENGTH = 16;
SALT_LENGTH = 8;
break;
case Zip4jConstants.AES_STRENGTH_256:
KEY_LENGTH = 32;
MAC_LENGTH = 32;
SALT_LENGTH = 16;
break;
default:
throw new ZipException("invalid aes key strength, cannot determine key sizes");
}
saltBytes = generateSalt(SALT_LENGTH);
byte[] keyBytes = deriveKey(saltBytes, password);
if (keyBytes == null || keyBytes.length != (KEY_LENGTH + MAC_LENGTH + PASSWORD_VERIFIER_LENGTH)) {
throw new ZipException("invalid key generated, cannot decrypt file");
}
aesKey = new byte[KEY_LENGTH];
macKey = new byte[MAC_LENGTH];
derivedPasswordVerifier = new byte[PASSWORD_VERIFIER_LENGTH];
System.arraycopy(keyBytes, 0, aesKey, 0, KEY_LENGTH);
System.arraycopy(keyBytes, KEY_LENGTH, macKey, 0, MAC_LENGTH);
System.arraycopy(keyBytes, KEY_LENGTH + MAC_LENGTH, derivedPasswordVerifier, 0, PASSWORD_VERIFIER_LENGTH);
aesEngine = new AESEngine(aesKey);
mac = new MacBasedPRF("HmacSHA1");
mac.init(macKey);
}
private byte[] deriveKey(byte[] salt, char[] password) throws ZipException {
try {
PBKDF2Parameters p = new PBKDF2Parameters("HmacSHA1", "ISO-8859-1",
salt, 1000);
PBKDF2Engine e = new PBKDF2Engine(p);
byte[] derivedKey = e.deriveKey(password, KEY_LENGTH + MAC_LENGTH + PASSWORD_VERIFIER_LENGTH);
return derivedKey;
} catch (Exception e) {
throw new ZipException(e);
}
}
public int encryptData(byte[] buff) throws ZipException {
if (buff == null) {
throw new ZipException("input bytes are null, cannot perform AES encrpytion");
}
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 += InternalZipConstants.AES_BLOCK_SIZE) {
loopCount = (j + InternalZipConstants.AES_BLOCK_SIZE <= (start + len)) ?
InternalZipConstants.AES_BLOCK_SIZE : ((start + len) - j);
Raw.prepareBuffAESIVBytes(iv, nonce, InternalZipConstants.AES_BLOCK_SIZE);
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 static byte[] generateSalt(int size) throws ZipException {
if (size != 8 && size != 16) {
throw new ZipException("invalid salt size, cannot generate salt");
}
int rounds = 0;
if (size == 8)
rounds = 2;
if (size == 16)
rounds = 4;
byte[] salt = new byte[size];
for( int j = 0; j < rounds; j++ ) {
Random rand = new Random();
int i = rand.nextInt();
salt[0+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 void setDerivedPasswordVerifier(byte[] derivedPasswordVerifier) {
this.derivedPasswordVerifier = derivedPasswordVerifier;
}
public byte[] getSaltBytes() {
return saltBytes;
}
public void setSaltBytes(byte[] saltBytes) {
this.saltBytes = saltBytes;
}
public int getSaltLength() {
return SALT_LENGTH;
}
public int getPasswordVeriifierLength() {
return PASSWORD_VERIFIER_LENGTH;
}
}