package jp.agentec.sinaburocast.common.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;

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

import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.util.Base64;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

public class EncryptUtil {

	/**
	 * 暗号化
	 *
	 * @param key 暗号キー
	 * @param text 文章
	 * @return 暗号されたデータ
	 * @throws Exception
	 */
	public static byte[] encrypt(String key, String text)
			throws IllegalBlockSizeException, InvalidKeyException,
			NoSuchAlgorithmException, UnsupportedEncodingException,
			BadPaddingException, NoSuchPaddingException {
		SecretKeySpec sksSpec = new javax.crypto.spec.SecretKeySpec(key
				.getBytes(), "Blowfish");
		Cipher cipher = javax.crypto.Cipher.getInstance("Blowfish");
		cipher.init(Cipher.ENCRYPT_MODE, sksSpec);
		byte[] encrypted = cipher.doFinal(text.getBytes());
		return encrypted;
	}

	/**
	 * 復号化
	 *
	 * @param key 暗号キー
 	 * @param encrypted 暗号されたデータ
	 * @return 元文章
	 * @throws Exception
	 */
	public static String decrypt(String key, byte[] encrypted)
			throws IllegalBlockSizeException, InvalidKeyException,
			NoSuchAlgorithmException, UnsupportedEncodingException,
			BadPaddingException, NoSuchPaddingException {
		SecretKeySpec sksSpec = new javax.crypto.spec.SecretKeySpec(key
				.getBytes(), "Blowfish");
		Cipher cipher = javax.crypto.Cipher.getInstance("Blowfish");
		cipher.init(javax.crypto.Cipher.DECRYPT_MODE, sksSpec);
		byte[] decrypted = cipher.doFinal(encrypted);
		return new String(decrypted);
	}

	/**
	 * BASE64復号化
	 *
	 * @param value
	 * @return
	 * @throws IOException
	 *             IO異常
	 * @throws Exception
	 */
	public static byte[] decryptBASE64(String value) throws IOException {
		return (new BASE64Decoder()).decodeBuffer(value);
	}

	/**
	 * BASE64復号化
	 *
	 * @param value
	 * @return
	 * @throws IOException
	 *             IO異常
	 * @throws NoSuchPaddingException
	 * @throws BadPaddingException
	 * @throws NoSuchAlgorithmException
	 * @throws IllegalBlockSizeException
	 * @throws InvalidKeyException
	 * @throws Exception
	 */
	public static String decryptBASE64(String key, String value)
		throws IOException, InvalidKeyException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, NoSuchPaddingException {
		return decrypt(key, new BASE64Decoder().decodeBuffer(value));
	}

	/**
	 * BASE64暗号化(改行は除去する)
	 *
	 * @param bytes
	 * @return
	 * @throws Exception
	 */
	public static String encryptBASE64(byte[] bytes) {
		return encryptBASE64(bytes, true);
	}

	/**
	 * BASE64暗号化
	 *
	 * @param bytes
	 * @param removeCRLF
	 * @return
	 */
	public static String encryptBASE64(byte[] bytes, boolean removeCRLF) {
		if (null == bytes || bytes.length < 1) return null;
		String ret = (new BASE64Encoder()).encodeBuffer(bytes).trim();
		if (removeCRLF) {
			ret = StringUtils.remove(ret, (char) Character.LETTER_NUMBER);
			ret = StringUtils.remove(ret, (char) Character.LINE_SEPARATOR);
		}
		return ret;
	}

	/**
	 * BASE64暗号化
	 *
	 * @param bytes
	 * @return
	 * @throws NoSuchPaddingException
	 * @throws BadPaddingException
	 * @throws UnsupportedEncodingException
	 * @throws NoSuchAlgorithmException
	 * @throws IllegalBlockSizeException
	 * @throws InvalidKeyException
	 * @throws Exception
	 */
	public static String encryptBASE64(String key, String text)
		throws InvalidKeyException, IllegalBlockSizeException, NoSuchAlgorithmException, UnsupportedEncodingException, BadPaddingException, NoSuchPaddingException {
		return encryptBASE64(encrypt(key, text));
	}

	/**
	 * Byteデータを結語する
	 * @param src1
	 * @param src2
	 * @return
	 */
	public static byte[] concatByte(byte[] src1, byte[] src2) {
		int length = src1.length + src2.length;
		byte[] rtnByte = new byte[length];

		for (int i = 0; i < length; i++) {
			if (i < src1.length) {
				rtnByte[i] = src1[i];
			} else {
				rtnByte[i] = src2[i - src1.length];
			}
		}

		return rtnByte;
	}

	/**
	 * Byteデータを抽出
	 *
	 * @param src1
	 * @param start スタート
	 * @param length 長さ
	 * @return
	 */
	public static byte[] extractByte(byte[] src1, int start, int length) {
		byte[] rtnByte = new byte[length];

		for (int i = start,idx=0; idx < length; i++,idx++) {
			if (i < src1.length)
				rtnByte[idx] = src1[i];
		}
		return rtnByte;
	}


    /**
     * MD5アルゴリズムを利用して暗号化を行う。
     *
     * @param str 暗号化される文字列
     * @return 暗号化結果
     */
	public static String crypt(String str) {
		MessageDigest md = null;
		try {
			md = MessageDigest.getInstance("MD5");
			md.update(str.getBytes());
			byte[] hash = md.digest();
			return hashByte2MD5(hash);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return null;
	}

	//MD5に利用されるハッシュ―関数
	public static String hashByte2MD5(byte []hash) {
		StringBuffer hexString = new StringBuffer();
		for (byte element : hash) {
			if ((0xff & element) < 0x10) {
				hexString.append("0" + Integer.toHexString((0xFF & element)));
			} else {
				hexString.append(Integer.toHexString(0xFF & element));
			}
		}

	    return hexString.toString().toUpperCase();
	}

	public static byte[] encryptCBC(byte[] keyBytes, byte[] textBytes) 
			throws java.io.UnsupportedEncodingException, 
				NoSuchAlgorithmException,
				NoSuchPaddingException,
				InvalidKeyException,
				InvalidAlgorithmParameterException,
				IllegalBlockSizeException,
				BadPaddingException {
		byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
		AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
    	SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
    	Cipher cipher = null;
		cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
		return cipher.doFinal(textBytes);
	}

	public static byte[] decryptCBC(byte[] keyBytes, byte[] textBytes) 
			throws java.io.UnsupportedEncodingException, 
			NoSuchAlgorithmException,
			NoSuchPaddingException,
			InvalidKeyException,
			InvalidAlgorithmParameterException,
			IllegalBlockSizeException,
			BadPaddingException {

		byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
		AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
		SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
		Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
		return cipher.doFinal(textBytes);
	}

	/**
	 * AES ECB(鍵長128bit)で暗号化しBase64データで返す
	 * 
	 * @param key
	 * @param data
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static String encryptECBAsBase64(String key, String data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
		return new String(Base64.encodeBase64(encryptECB(key.getBytes(), data.getBytes())));
	}
	
	/**
	 * AES ECB(鍵長128bit)のBase64データを復号化する
	 * 
	 * @param key
	 * @param data
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static String decryptECBAsBase64(String key, String data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
		return new String(decryptECB(key.getBytes(), Base64.decodeBase64(data)));
	}

	
	/**
	 * AES ECB(鍵長128bit)で暗号化する
	 * 
	 * @param key
	 * @param data
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] encryptECB(byte[] secret_key, byte[] data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
	     SecretKeySpec sKey = new SecretKeySpec(secret_key, "AES");
	      Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
	      cipher.init(Cipher.ENCRYPT_MODE, sKey);
	      return cipher.doFinal(data);
	 }

	/**
	 * AES ECB(鍵長128bit)のデータを復号化する
	 * 
	 * @param key
	 * @param data
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	 public static byte[] decryptECB(byte[] secret_key, byte[] encData) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
	     SecretKeySpec sKey = new SecretKeySpec(secret_key, "AES");
	      Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
	      cipher.init(Cipher.DECRYPT_MODE, sKey);
	      return cipher.doFinal(encData);
	 }
	
	public static void main(String[] args) {
		if (args.length != 2) {
			System.out.println("key and text are required.");
			return;
		}

		try {
			System.out.println("Encrypt: " + encryptBASE64(args[0], args[1]));
		} catch (Exception e) {
			System.out.println(e.toString());
		}

		try {
			System.out.println("Encrypt: " + decryptBASE64(args[0], args[1]));
		} catch (Exception e) {
			System.out.println(e.toString());
		}
	}


}