/*********************************************************************
* All Rights reserved,Copyright (c) K-Opticom
**********************************************************************
*＜プログラム内容＞
*   システム名      ：eo顧客基幹システム
*   モジュール名    ：JFUdecryptUtil
*   ソースファイル名：JFUdecryptUtil.java
*   作成者          ：富士通
*   日付            ：2012年04月04日
*＜機能概要＞
*   AES複号化部品です。
*＜修正履歴＞
*   バージョン  修正日       修正者      修正内容
*   v3.00       2012/04/04   FJ）西川    新規作成 【ANK-0235-00-00】eoモバイルWiMAX受付対応(W10055)
*
**********************************************************************/
package eo.web.webview.common;

import static com.fujitsu.futurity.web.x31.X31SWebLog.DEBUG_LOG;

import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import sun.misc.BASE64Decoder;

import com.fujitsu.futurity.web.x00.JCCBusinessException;

import eo.common.constant.JFUStrConst;

/**
 * AES複号化部品です。
 * <br>
 * @author 富士通
 */
public class JFUdecryptUtil
{
	/** dump binary data */
	private static final sun.misc.HexDumpEncoder HEX_DUMP = new sun.misc.HexDumpEncoder();

	/** Cipher オブジェクト 変換文字列 */
	private static final String TRANSFORMATION = "/CBC/PKCS5Padding";

	/** アルゴリズム指定文字列：AES */
	private static final String ALGORITHM_AES = "AES";

	/**
	 * BASE64符号化された暗号化文字列をAES256bit,CBCモードで復号化します。 <BR>
	 *
	 * @param encryptedString BASE64符号化された暗号化文字列
	 *
	 * @return 復号化された文字列
	 * @throws Exception 復号化の際に発生した例外
	 */
	public static String decryptBase64String(String encryptedString) throws Exception
	{

		DEBUG_LOG.info("【復号化文字列】前：" + encryptedString);

		if (0 == JFUWebCommon.getLength(encryptedString))
		{
			DEBUG_LOG.info("引数が不正です。(複号化対象の文字列が未設定)");
			// システムエラー
			throw new JCCBusinessException(JFUStrConst.ERROR_CODE_0002);
		}
		
		String decryptedString = null;

		try
		{
			// BASE64復号化
			byte[] encryptText = new BASE64Decoder().decodeBuffer(encryptedString);

			// 復号化実行
			decryptedString = decrypt(encryptText, readAES256Key());

		}
		catch (Exception e)
		{
			DEBUG_LOG.error("AES復号化時に想定外のシステムエラーが発生しました。[例外Class]" + e.getClass());
			DEBUG_LOG.error("スタックトレースを出力します");
			DEBUG_LOG.error((Object[])e.getStackTrace());
			// システムエラー
			throw new JCCBusinessException(JFUStrConst.ERROR_CODE_0002);
		}

		DEBUG_LOG.info("【復号化文字列】後：" + decryptedString);

		return decryptedString;
	}

	/**
	* 引数のbyte配列をAES256bit,CBCモードで復号化します。 <BR>
	*
	* @param encryptedText 暗号化されたバイトコード
	* @param key 秘密鍵
	*
	* @return 復号化された文字列
	*
	* @throws NoSuchAlgorithmException				変換名が null、空、形式が不正、または現在インストールされているプロバイダで使用不可能な場合
	* @throws NoSuchPaddingException				使用できないパディング方式が transformation に含まれている場合
	* @throws InvalidKeyException					指定された鍵がこの暗号の初期化に不適切な場合
	* @throws IllegalBlockSizeException			この暗号で処理されたデータの入力長の合計がブロックサイズの倍数でない場合、
	* 												または、この暗号化アルゴリズムでは提供された入力データを処理できない場合
	* @throws BadPaddingException					復号化されたデータが適切なパディングバイトでバインドされない場合
	* @throws IOException							復号化エラーが発生した場合、あるいはこのパラメータオブジェクトがすでに初期化されている場合
	* @throws InvalidAlgorithmParameterException	指定されたアルゴリズムパラメータがこの暗号に不適切な場合、
	* 												この暗号が復号化用に初期化され、アルゴリズムパラメータを必要とし、params が null の場合、
	* 												または指定されたアルゴリズムパラメータが有効な制限 
	* 												(設定されている管轄ポリシーファイルにより決定) を超える暗号化強度を示す場合
	*/
	private static String decrypt(byte[] encryptedText, SecretKey key) throws NoSuchAlgorithmException, NoSuchPaddingException,
																					InvalidKeyException,
																					IllegalBlockSizeException, BadPaddingException, IOException,
																					InvalidAlgorithmParameterException
	{

		// AES/CBC/PKCS5Padding
		Cipher cipher = Cipher.getInstance(key.getAlgorithm() + TRANSFORMATION);

		// IV
		AlgorithmParameters iv = AlgorithmParameters.getInstance(key.getAlgorithm());
		iv.init(readInitialValue());
		
		// JCE管轄ポリシーファイルに基く、最大の鍵長をデバッグ出力(デフォルト鍵サイズでは256bitに対応していない)
		DEBUG_LOG.debug("【MaxAllowedKeyLength】" + Cipher.getMaxAllowedKeyLength(ALGORITHM_AES));

		// Decrypt data
		cipher.init(Cipher.DECRYPT_MODE, key, iv);

		byte[] decryptedText = cipher.doFinal(encryptedText);
		DEBUG_LOG.debug("【DECRYPTED DATA】");
		DEBUG_LOG.debug(HEX_DUMP.encode(decryptedText));

		return new String(decryptedText);
	}

	/**
	 * イニシャルバリューを作成する。 <BR>
	 *
	 * @return iv イニシャルバリュー
	 */
	private static byte[] readInitialValue()
	{

		// プロパティファイルよりイニシャルバリューを取得
		String iv = JFUWebCommon.getApplicationConst(JFUStrConst.FU_N_UQ_INITIAL_VALUE);
		
		if (0 == JFUWebCommon.getLength(iv))
		{
			DEBUG_LOG.info("プロパティファイルからイニシャルバリューを取得できません");
		}

		return asByteArray(iv);
	}

	/**
	 * AES256bit秘密鍵を作成します。 <BR>
	 *
	 * @return key 秘密鍵
	 *
	 * @throws InvalidKeySpecException		指定された鍵がこの暗号の初期化に不適切な場合
	 * @throws NoSuchAlgorithmException	変換名が null、空、形式が不正、または現在インストールされているプロバイダで使用不可能な場合
	 */
	private static SecretKey readAES256Key() throws InvalidKeySpecException, NoSuchAlgorithmException
	{

		// アプリケーションプロパティから秘密鍵を取得
		String key = JFUWebCommon.getApplicationConst(JFUStrConst.FU_N_UQ_SECRET_KEY);

		if (0 == JFUWebCommon.getLength(key))
		{
			DEBUG_LOG.info("プロパティファイルから秘密鍵を取得できません");
		}

		return new SecretKeySpec(asByteArray(key), ALGORITHM_AES);
	}

	/**
	 * 16進数の文字列をバイト配列に変換します。
	 *
	 * @param hex 16進数の文字列
	 * @return バイト配列
	 */
	private static byte[] asByteArray(String hex)
	{
		// 文字列長の1/2の長さのバイト配列を生成。
		byte[] bytes = new byte[hex.length() / 2];

		// バイト配列の要素数分、処理を繰り返す。
		for (int index = 0; index < bytes.length; index++)
		{
			// 16進数文字列をバイトに変換して配列に格納。
			bytes[index] = (byte)Integer.parseInt(hex.substring(index * 2, (index + 1) * 2), 16);
		}

		// バイト配列を返す。
		return bytes;
	}

}
