package jp.agentec.adf.security.cryptography;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

import jp.agentec.adf.util.ArrayUtil;
import jp.agentec.adf.util.StringUtil;

/**
 * AESアルゴリズムを使って、文字列を暗号化・復号化する機能を提供します。
 * @author Taejin Hong
 * @version 1.1.0
 */
public class AES {
	/**
	 * AESアルゴリズムに利用するキーの長さを示します。
	 * @author Taejin Hong
	 * @version 1.0.0
	 */
	public enum KeySize {
		/**
		 * 128bitのキーを示します。
		 * @since 1.0.0
		 */
		Key128bit(128),
		/**
		 * 196bitのキーを示します。
		 * @since 1.0.0
		 */
		Key192bit(192),
		/**
		 * 256bitのキーを示します。
		 * @since 1.0.0
		 */
		Key256bit(256);
		
		private final int keySize;
		
		KeySize(int size) {
			this.keySize = size;
		}
		
		/**
		 * keyの長さ（bit）を返します。
		 * @return　keyの長さ（bit）です。
		 * @since 1.0.0
		 */
		public int size() {
			return keySize;
		}
	}
	
	private static final String AES = "AES";
	private static final String ECB = "AES/ECB/PKCS5Padding";
	private static final int DefaultKeySize = KeySize.Key128bit.size();
	
	/**
	 * 暗号化・復号化のキーをランダムで作成します。キーのサイズは128bitとなります。
	 * @return 暗号化・復号化のキーを返します。
	 * @throws NoSuchAlgorithmException　AESアルゴリズムが使えません。
	 * @since 1.0.0
	 */
	public static Key generateKey() throws NoSuchAlgorithmException {
		return generateKey(DefaultKeySize);
	}
	
	/**
	 * 暗号化・復号化のキーをランダムで作成します。キーのサイズは128bitとなります。
	 * @param keySize　暗号化・復号化のキーサイズ（bit）です。
	 * @return 暗号化・復号化のキーを返します。
	 * @throws NoSuchAlgorithmException　AESアルゴリズムが使えません。
	 * @since 1.0.0
	 */
	public static Key generateKey(KeySize keySize) throws NoSuchAlgorithmException {
		return generateKey(keySize.size());
	}
	
	/**
	 * 暗号化・復号化のキーをランダムで作成します。キーのサイズは128bitとなります。
	 * @param keySize　暗号化・復号化のキーサイズ（bit）です。
	 * @return 暗号化・復号化のキーを返します。
	 * @throws NoSuchAlgorithmException　AESアルゴリズムが使えません。
	 * @since 1.0.0
	 */
	private static Key generateKey(int keySize) throws NoSuchAlgorithmException {
		Key secureKey = null;
		
		if (validateKeySize(keySize)) {
			KeyGenerator generator = KeyGenerator.getInstance(AES);
			generator.init(keySize, SecureRandom.getInstance("SHA1PRNG"));
			secureKey =  generator.generateKey();
		}
		
		return secureKey;
	}
	
	/**
	 * 指定した文字列を利用して、暗号化・復号化のキーをランダムで作成します。
	 * @param keyString　キーとして利用する文字列です。文字列の長さは128bit(16byte)、192bit(24byte)、256bit(32byte)のいずれかにあたる必要があります。
	 * @return 暗号化・復号化のキーを返します。
	 * @throws NoSuchAlgorithmException　AESアルゴリズムが使えません。
	 * @since 1.0.0
	 */
	public static Key generateKey(String keyString) throws NoSuchAlgorithmException {
		Key secureKey = null;
		
		if (validateKeyString(keyString)) {
			secureKey = new SecretKeySpec(keyString.getBytes(), AES);
		}
		
		return secureKey;
	}
	
	/**
	 * 指定した文字列が、暗号化・復号化のキーとして使えるかを示します。
	 * @param keyString　テストする文字列です。
	 * @return 指定した文字列の長さがの128bit(16byte)、192bit(24byte)、256bit(32byte)のいずれかの場合、trueを返します。
	 * @since 1.0.0
	 */
	private static boolean validateKeyString(String keyString) {
		boolean result = false;
		
		if (!StringUtil.isNullOrEmpty(keyString)
				&& validateKeySize(keyString.length())) {
			result = true;
		}
		
		return result;
	}
	
	/**
	 * 指定したキーのサイズが、暗号化・復号化のキーとして使えるかを示します。
	 * @param keySize　キーのサイズです。
	 * @return サイズが128(bit)、192(bit)、256(bit)のいずれかの場合、trueを返します。
	 * @since 1.0.0
	 */
	private static boolean validateKeySize(int keySize) {
		boolean result = false;
				
		if (keySize == KeySize.Key128bit.size() / 8
			|| keySize == KeySize.Key192bit.size() / 8
			|| keySize == KeySize.Key256bit.size() / 8) {
			result = true;
		}
		
		return result;
	}
	
	/**
	 * 指定した文字列を、指定したキーを利用して暗号化(AES)します。
	 * @param input 暗号化する文字列です。
	 * @param secureKey 暗号化キーです。
	 * @return 暗号化されたバイト配列を返します。
	 * @since 1.0.0
	 */
	public static byte[] encrypt(String input, Key secureKey)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		byte[] encrypted = null;
		
		if (!StringUtil.isNullOrEmpty(input) && secureKey != null) {
			encrypted = encrypt(input.getBytes(), secureKey);
		}

		return encrypted;
	}
	
	/**
	 * 指定しバイト配列を、指定したキーを利用して暗号化(AES)します。
	 * @param input 暗号化するバイト配列です。
	 * @param secureKey　暗号化キーです。
	 * @return　暗号化されたバイト配列を返します。
	 * @since 1.0.0
	 */
	public static byte[] encrypt(byte[] input, Key secureKey)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		byte[] encrypted = null;
		
		if (!ArrayUtil.isNullOrEmpty(input) && secureKey != null) {
			Cipher cipher = Cipher.getInstance(AES);
			cipher.init(Cipher.ENCRYPT_MODE, secureKey);
			encrypted = cipher.doFinal(input);
		}

		return encrypted;
	}
	
	/**
	 * 指定した文字列を、指定したキーを利用して暗号化(AES ECB(鍵長128bit))します。
	 * @param input 暗号化する文字列です。
	 * @param secureKey 暗号化キーです。
	 * @return 暗号化されたバイト配列を返します。
	 * @since 1.1.0
	 */
	public static byte[] encryptECB(String input, Key secureKey)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		byte[] encrypted = null;
		
		if (!StringUtil.isNullOrEmpty(input) && secureKey != null) {
			encrypted = encryptECB(input.getBytes(), secureKey);
		}

		return encrypted;
	}
	
	/**
	 * 指定したバイト配列を、指定したキーを利用して暗号化(AES ECB(鍵長128bit))します。
	 * @param input 暗号化するバイト配列です。
	 * @param secureKey 暗号化キーです。
	 * @return 暗号化されたバイト配列を返します。
	 * @since 1.1.0
	 */
	public static byte[] encryptECB(byte[] input, Key secureKey)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		byte[] encrypted = null;
		
		if (!ArrayUtil.isNullOrEmpty(input) && secureKey != null) {
			Cipher cipher = Cipher.getInstance(ECB);
			cipher.init(Cipher.ENCRYPT_MODE, secureKey);
			encrypted = cipher.doFinal(input);
		}
		
		return encrypted;
	}
	
	/**
	 * 指定した暗号化(AES)された文字列を、指定したキーを利用して復号化(AES)します。
	 * @param encrypted　暗号化された文字列です。
	 * @param secureKey　復号化キーです。
	 * @return　復号化されたバイト配列を返します。
	 * @since 1.0.0
	 */
	public static byte[] decrypt(String encrypted, Key secureKey)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		byte[] decrypted = null;
		
		if (!StringUtil.isNullOrEmpty(encrypted) && secureKey != null) {
			decrypted = decrypt(encrypted.getBytes(), secureKey);
		}
		
		return decrypted;
	}
	
	/**
	 * 指定した暗号化(AES)されたバイト配列を、指定したキーを利用して復号化(AES)します。
	 * @param encrypted　暗号化されたバイト配列です。
	 * @param secureKey　復号化キーです。
	 * @return　復号化されたバイト配列を返します。
	 * @since 1.0.0
	 */
	public static byte[] decrypt(byte[] encrypted, Key secureKey)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		byte[] decrypted = null;
		
		if (!ArrayUtil.isNullOrEmpty(encrypted) && secureKey != null) {
			Cipher cipher = Cipher.getInstance(AES);
			cipher.init(Cipher.DECRYPT_MODE, secureKey);
			decrypted = cipher.doFinal(encrypted);
		}
		
		return decrypted;
	}
	
	/**
	 * 指定した暗号化(AES ECB(鍵長128bit))された文字列を、指定したキーを利用して復号化(AES)します。
	 * @param encrypted　暗号化された文字列です。
	 * @param secureKey　復号化キーです。
	 * @return　復号化されたバイト配列を返します。
	 * @since 1.1.0
	 */
	public static byte[] decryptECB(String encrypted, Key secureKey)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		byte[] decrypted = null;
		
		if (!StringUtil.isNullOrEmpty(encrypted) && secureKey != null) {
			decrypted = decryptECB(encrypted.getBytes(), secureKey);
		}
		
		return decrypted;
	}
	
	/**
	 * 指定した暗号化(AES ECB(鍵長128bit))されたバイト配列を、指定したキーを利用して復号化(AES)します。
	 * @param encrypted　暗号化されたバイト配列です。
	 * @param secureKey　復号化キーです。
	 * @return　復号化されたバイト配列を返します。
	 * @since 1.1.0
	 */
	public static byte[] decryptECB(byte[] encrypted, Key secureKey)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		byte[] decrypted = null;
		
		if (!ArrayUtil.isNullOrEmpty(encrypted) && secureKey != null) {
			Cipher cipher = Cipher.getInstance(ECB);
			cipher.init(Cipher.DECRYPT_MODE, secureKey);
			decrypted = cipher.doFinal(encrypted);
		}
		
		return decrypted;
	}
}
