All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nervousync.zip.crypto.impl.aes.AESCrypto Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
/*
 * Licensed to the Nervousync Studio (NSYC) 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 org.nervousync.zip.crypto.impl.aes;

import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;

import org.nervousync.commons.core.Globals;
import org.nervousync.exceptions.crypto.CryptoException;
import org.nervousync.security.SecureProvider;
import org.nervousync.security.digest.BaseDigestProvider;
import org.nervousync.utils.SecurityUtils;
import org.nervousync.zip.engine.AESEngine;
import org.nervousync.exceptions.zip.ZipException;
import org.nervousync.utils.RawUtils;

/**
 * AES Crypto
 *
 * @author Steven Wee	[email protected]
 * @version $Revision : 1.0 $ $Date: Dec 2, 2017 11:16:18 AM $
 */
public class AESCrypto {

	/**
	 * Salt data array
	 */
	private byte[] saltBytes;
	
	/**
	 * key length
	 */
	private int keyLength;
	/**
	 * mac length
	 */
	private int macLength;
	/**
	 * salt length
	 */
	int saltLength;

	/**
	 * current password bytes
	 */
	byte[] derivedPasswordVerifier = null;

	/**
	 * nonce
	 */
	int nonce = 1;
	/**
	 * loop count
	 */
	int loopCount = 0;

	/**
	 * iv bytes
	 */
	byte[] iv = null;
	/**
	 * count block bytes
	 */
	byte[] countBlock = null;

	/**
	 * AES engine
	 */
	AESEngine aesEngine = null;
	/**
	 * MacBasedPRF instance
	 */
	SecureProvider macBasedPRF = null;

	/**
	 * Instantiates a new Aes crypto.
	 */
	protected AESCrypto() {
	}

	/**
	 * Verify given password
	 *
	 * @param aesStrength   AES key strength
	 * @param salt          salt bytes
	 * @param password      password
	 * @param passwordBytes password bytes
	 * @return verify result
	 */
	public static boolean verifyPassword(int aesStrength, byte[] salt, char[] password, byte[] passwordBytes) {
		if (password == null || password.length == 0 || passwordBytes == null || passwordBytes.length == 0) {
			return Boolean.FALSE;
		}
		AESCrypto aesCrypto = new AESCrypto();
		aesCrypto.preInit(aesStrength);
		aesCrypto.init(salt, password);
		return aesCrypto.verifyPassword(passwordBytes);
	}

	/**
	 * Get salt bytes byte [ ].
	 *
	 * @return the saltBytes
	 */
	public byte[] getSaltBytes() {
		return saltBytes == null ? new byte[0] : saltBytes.clone();
	}

	/**
	 * Prepare initialize
	 *
	 * @param aesStrength AES key strength
	 */
	void preInit(int aesStrength) {
		this.iv = new byte[Globals.AES_BLOCK_SIZE];
		this.countBlock = new byte[Globals.AES_BLOCK_SIZE];
		
		switch (aesStrength) {
			case Globals.AES_STRENGTH_128:
				this.keyLength = 16;
				this.macLength = 16;
				this.saltLength = 8;
				break;
			case Globals.AES_STRENGTH_192:
				this.keyLength = 24;
				this.macLength = 24;
				this.saltLength = 12;
				break;
			case Globals.AES_STRENGTH_256:
				this.keyLength = 32;
				this.macLength = 32;
				this.saltLength = 16;
				break;
			default:
				throw new ZipException("Invalid aes key strength!");
		}
	}

	/**
	 * Initialize by given password
	 *
	 * @param password password
	 * @throws ZipException the zip exception
	 */
	void init(char[] password) throws ZipException {
		if (password == null || password.length == 0) {
			throw new ZipException("Password is null or empty");
		}
		this.generateSalt();
		try {
			this.initCrypto(password);
		} catch (CryptoException e) {
			throw new ZipException(e);
		}
	}

	/**
	 * Initialize by given password and salt
	 *
	 * @param salt     salt bytes
	 * @param password password char arrays
	 * @throws ZipException the zip exception
	 */
	void init(byte[] salt, char[] password) throws ZipException {
		if (password == null || password.length == 0) {
			throw new ZipException("Password is null or empty");
		}
		this.saltBytes = salt == null ? new byte[0] : salt.clone();
		try {
			this.initCrypto(password);
		} catch (CryptoException e) {
			throw new ZipException(e);
		}
	}

	/**
	 * Process data.
	 *
	 * @param buff  the buff
	 * @param index the index
	 */
	void processData(byte[] buff, int index) {
		this.iv = RawUtils.intToByteArray(this.nonce, 16);
		this.aesEngine.processBlock(this.iv, this.countBlock);

		for (int j = 0 ; j < this.loopCount ; j++) {
			buff[index + j] = (byte)(buff[index + j] ^ this.countBlock[j]);
		}
		this.nonce++;
	}
	
	/**
	 * Derive key
	 * @param saltBytes		salt bytes
	 * @param password		password
	 * @param dkLen			length
	 * @return				processed data bytes
	 */
	private byte[] deriveKey(byte[] saltBytes, char[] password, int dkLen) throws CryptoException {
		//	PBKDF2
		if (password == null || password.length == 0) {
			throw new NullPointerException();
		}
		byte[] passwordBytes = RawUtils.charArrayToByteArray(password);
		BaseDigestProvider digestProvider = (BaseDigestProvider) SecurityUtils.HmacSHA1(passwordBytes);

		if (dkLen == 0) {
			dkLen = digestProvider.macLength();
		}

		if (saltBytes == null) {
			saltBytes = new byte[0];
		}

		int length = digestProvider.macLength();
		int l = ceil(dkLen, length);
		int r = dkLen - (l - 1) * length;
		byte[] tempBytes = new byte[l * length];
		int offset = 0;
		for (int i = 1 ; i <= l ; i++) {
			process(digestProvider, tempBytes, offset, saltBytes, i);
			offset += length;
		}

		if (r < length) {
			byte[] bytes = new byte[dkLen];
			System.arraycopy(tempBytes, 0, bytes, 0, dkLen);
			return bytes;
		}
		return tempBytes;
	}

	/**
	 * Verify given password
	 *
	 * @param password password
	 * @return verify result
	 */
	boolean verifyPassword(byte[] password) {
		if (this.derivedPasswordVerifier == null) {
			throw new ZipException("Invalid derived password verifier!");
		}

		return Arrays.equals(password, this.derivedPasswordVerifier);
	}

	private static int ceil(int a, int b) {
		int m = 0;
		if (a % b > 0) {
			m = 1;
		}
		return a / b + m;
	}

	private static void process(BaseDigestProvider baseDigestProvider,
								byte[] dest, int offset, byte[] source, int blockIndex) throws CryptoException {
		int length = baseDigestProvider.macLength();
		byte[] tempBytes = new byte[length];

		byte[] intTmpBytes = new byte[source.length + 4];
		System.arraycopy(source, 0, intTmpBytes, 0, source.length);
		RawUtils.writeInt(intTmpBytes, source.length, blockIndex);

		for (int i = 0 ; i < 1000 ; i++) {
			intTmpBytes = baseDigestProvider.finish(intTmpBytes);
			XOR(tempBytes, intTmpBytes);
		}
		System.arraycopy(tempBytes, 0, dest, offset, length);
	}

	private static void XOR(byte[] dest, byte[] source) {
		for (int i = 0 ; i < dest.length ; i++) {
			dest[i] ^= source[i];
		}
	}

	/**
	 * Initialize crypto
	 * @param password	password
	 */
	private void initCrypto(char[] password) throws CryptoException {
		byte[] keyBytes = this.deriveKey(this.saltBytes, password, 
				this.keyLength + this.macLength + Globals.PASSWORD_VERIFIER_LENGTH);
		
		if (keyBytes.length != (this.keyLength + this.macLength + Globals.PASSWORD_VERIFIER_LENGTH)) {
			throw new ZipException("Invalid derived key!");
		}

		byte[] aesKey = new byte[this.keyLength];
		byte[] macKey = new byte[this.macLength];
		this.derivedPasswordVerifier = new byte[Globals.PASSWORD_VERIFIER_LENGTH];
		
		System.arraycopy(keyBytes, 0, aesKey, 0, this.keyLength);
		System.arraycopy(keyBytes, this.keyLength, macKey, 0, this.macLength);
		System.arraycopy(keyBytes, (this.keyLength + this.macLength), 
				this.derivedPasswordVerifier, 0, Globals.PASSWORD_VERIFIER_LENGTH);
		
		this.aesEngine = new AESEngine(aesKey);
		this.macBasedPRF = SecurityUtils.HmacSHA1(macKey);
	}
	
	/**
	 * Generate salt data
	 * @throws ZipException if salt length was invalid
	 */
	private void generateSalt() throws ZipException {
		int rounds = this.saltLength / 4;
		if (rounds < 2 || rounds > 4) {
			throw new ZipException("Invalid salt size!");
		}

		this.saltBytes = new byte[this.saltLength];
		for (int i = 0 ; i < rounds ; i++) {
			Random random = new SecureRandom();
			int temp = random.nextInt();
			this.saltBytes[i * 4] = (byte)(temp >> 24);
			this.saltBytes[1 + i * 4] = (byte)(temp >> 16);
			this.saltBytes[2 + i * 4] = (byte)(temp >> 8);
			this.saltBytes[3 + i * 4] = (byte)temp;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy