content: /*
  * 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 
  * 
  * https://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;
  	}
  }
  