/**
 *
 */
package com.allinpay.demo.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Enumeration;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import org.apache.commons.io.IOUtils;


/**
 * @author Administrator
 *
 */
@SuppressWarnings("rawtypes")
public class Crypt
{
	/**
	 * 构造函数
	 */
	public Crypt()
	{
	}

	public Crypt(String encoding)
	{
		this.encoding = encoding;
	}

	private String encoding = "GBK";

	/**
	 * 对字符串进行加密
	 *
	 * @param TobeEncrypted
	 *            待加密的字符串
	 * @param CertFile
	 *            解密者公钥证书路径
	 * @return 加密成功返回true(从LastResult属性获取结果)，失败返回false(从LastErrMsg属性获取失败原因)
	 * @throws Exception
	 */
	public String encryptMsg(final String TobeEncrypted, final String CertFile) throws Exception
	{
		FileInputStream certfile = null;
		try
		{
			certfile = new FileInputStream(CertFile);

			CertificateFactory cf = CertificateFactory.getInstance("X.509");

			X509Certificate x509cert = (X509Certificate) cf.generateCertificate(certfile);
			RSAPublicKey pubkey = (RSAPublicKey) x509cert.getPublicKey();
			BigInteger mod = pubkey.getModulus();

			int keylen = mod.bitLength() / 8;
			if (TobeEncrypted.length() > keylen - 11)
			{
				// 创建Cipher 对象（确定算法[RSA]、方式[NONE]和填充[PKCS1Padding]）
				Cipher pub = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
				pub.init(Cipher.WRAP_MODE, pubkey);

				// get a DES private key
				KeyGenerator kp = KeyGenerator.getInstance("DESEDE");
				kp.init(new SecureRandom());// SecureRandom提供加密的强随机数生成器 (RNG)
				SecretKey sk = kp.generateKey();
				// 将密钥包装
				byte wrappedKey[] = pub.wrap(sk);

				// 创建Cipher 对象（确定算法[DESEDE]、方式[OFB:输出反馈方式(Output Feedbac
				// Mode)]和填充[NoPadding]）
				pub = Cipher.getInstance("DESEDE/OFB/NoPadding");
				pub.init(Cipher.ENCRYPT_MODE, sk);

				// int input = TobeEncrypted.length();
				// 按单部分操作加密或解密数据，或者结束一个多部分操作。数据被加密还是解密取决于此 cipher 的初始化方式
				byte encrypted[] = pub.doFinal(TobeEncrypted.getBytes(encoding));
				byte iv[] = pub.getIV();// 返回新缓冲区中的初始化向量 (IV)
				byte enc_ascii[] = new byte[encrypted.length * 2];
				byte iv_asc[] = new byte[iv.length * 2];
				byte prikey_asc[] = new byte[wrappedKey.length * 2];
				Hex2Ascii(encrypted.length, encrypted, enc_ascii);
				Hex2Ascii(iv.length, iv, iv_asc);
				Hex2Ascii(wrappedKey.length, wrappedKey, prikey_asc);
				return new String(iv_asc) + new String(prikey_asc) + new String(enc_ascii);
			}
			else
			{
				Cipher pub = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
				pub.init(Cipher.ENCRYPT_MODE, pubkey);
				byte encrypted[] = pub.doFinal(TobeEncrypted.getBytes(encoding));
				byte enc_ascii[] = new byte[encrypted.length * 2];
				Hex2Ascii(encrypted.length, encrypted, enc_ascii);
				return new String(enc_ascii);

			}
		}
		finally
		{
			IOUtils.closeQuietly(certfile);
		}
	}

	/**
	 * 对加密后的密文进行解密
	 *
	 * @param TobeDecrypted
	 *            需要解密的密文
	 * @param KeyFile
	 *            PFX证书文件路径
	 * @param PassWord
	 *            私钥保护密码
	 * @return 解密成功返回true(从LastResult属性获取结果)，失败返回false(从LastErrMsg属性获取失败原因)
	 * @throws Exception
	 */
	public String decryptMsg(final String TobeDecrypted, final String KeyFile, final String PassWord) throws Exception
	{
		FileInputStream fiKeyFile = null;
		try
		{
			KeyStore ks = KeyStore.getInstance("PKCS12");
			// ks.load(new FileInputStream(KeyFile), PassWord.toCharArray());
			fiKeyFile = new FileInputStream(KeyFile);
			ks.load(fiKeyFile, PassWord.toCharArray());
			Enumeration myEnum = ks.aliases();
			String keyAlias = null;
			RSAPrivateCrtKey prikey = null;
			// keyAlias = (String) myEnum.nextElement();
			/* IBM JDK必须使用While循环取最后一个别名，才能得到个人私钥别名 */
			while (myEnum.hasMoreElements())
			{
				keyAlias = (String) myEnum.nextElement();
				// System.out.println("keyAlias==" + keyAlias);
				if (ks.isKeyEntry(keyAlias))
				{
					prikey = (RSAPrivateCrtKey) ks.getKey(keyAlias, PassWord.toCharArray());
					break;
				}
			}
			if (prikey == null)
			{
				throw new Exception("没有找到匹配私钥");
			}
			else
			{
				// RSAPrivateCrtKey prikey = (RSAPrivateCrtKey)
				// ks.getKey(keyAlias,
				// PassWord.toCharArray());
				BigInteger mod = prikey.getModulus();
				int keylen = mod.bitLength() / 8;
				if (TobeDecrypted.length() > keylen * 2)
				{
					byte iv_asc[] = TobeDecrypted.substring(0, 16).getBytes(encoding);
					byte prikey_asc[] = TobeDecrypted.substring(iv_asc.length, iv_asc.length + keylen * 2).getBytes(encoding);
					byte enc_ascii[] = TobeDecrypted.substring(iv_asc.length + keylen * 2).getBytes();

					/*
					 * b_decin.read(iv_asc, 0, 16); b_decin.read(prikey_asc, 0,
					 * 256); b_decin.read(enc_ascii, 0, in_len - 16 - 256);
					 */
					byte iv[] = new byte[8];
					byte unwrappedkey[] = new byte[prikey_asc.length / 2];
					byte decrypted[] = new byte[enc_ascii.length / 2];

					Ascii2Hex(iv_asc.length, iv_asc, iv);
					Ascii2Hex(prikey_asc.length, prikey_asc, unwrappedkey);
					Ascii2Hex(enc_ascii.length, enc_ascii, decrypted);

					Cipher pri = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
					pri.init(Cipher.UNWRAP_MODE, prikey);
					Key unwrappedKey = pri.unwrap(unwrappedkey, "DESEDE", Cipher.SECRET_KEY);
					// 此类指定一个初始化向量 (IV)。使用 IV 的例子是反馈模式中的密码，如，CBC 模式中的 DES 和使用
					// OAEP
					// 编码操作的 RSA 密码
					IvParameterSpec ivspec = new IvParameterSpec(iv);
					pri = Cipher.getInstance("DESEDE/OFB/NoPadding");
					pri.init(Cipher.DECRYPT_MODE, unwrappedKey, ivspec);
					// byte decryptout[] = ;

					return new String(pri.doFinal(decrypted));

				}
				else
				{
					Cipher pri = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
					pri.init(Cipher.DECRYPT_MODE, prikey);
					byte enc_ascii[] = TobeDecrypted.getBytes(encoding);
					byte decrypted[] = new byte[enc_ascii.length / 2];
					Ascii2Hex(enc_ascii.length, enc_ascii, decrypted);
					byte decryptout[] = pri.doFinal(decrypted);

					return new String(decryptout);

				}
			}

		}
		finally
		{
			IOUtils.closeQuietly(fiKeyFile);
		}
	}
	public static RSAPrivateCrtKey readPrivKey(final String KeyFile, final String PassWord) throws Exception
	{
		FileInputStream fiKeyFile = null;
		RSAPrivateCrtKey prikey = null;
		try
		{
			KeyStore ks = KeyStore.getInstance("PKCS12");
			// ks.load(new FileInputStream(KeyFile), PassWord.toCharArray());
			fiKeyFile = new FileInputStream(KeyFile);
			ks.load(fiKeyFile, PassWord.toCharArray());
			Enumeration myEnum = ks.aliases();
			String keyAlias = null;
			// keyAlias = (String) myEnum.nextElement();
			/* IBM JDK必须使用While循环取最后一个别名，才能得到个人私钥别名 */
			while (myEnum.hasMoreElements())
			{
				keyAlias = (String) myEnum.nextElement();
				// System.out.println("keyAlias==" + keyAlias);
				if (ks.isKeyEntry(keyAlias))
				{
					prikey = (RSAPrivateCrtKey) ks.getKey(keyAlias, PassWord.toCharArray());
					break;
				}
			}
		}
		finally
		{
			IOUtils.closeQuietly(fiKeyFile);
		}
		return prikey;
	}
	/**
	 * 对字符串进行签名
	 *
	 * @param TobeSigned
	 *            需要进行签名的字符串
	 * @param KeyFile
	 *            PFX证书文件路径
	 * @param PassWord
	 *            私钥保护密码
	 * @return 签名成功返回true(从LastResult属性获取结果)，失败返回false(从LastErrMsg属性获取失败原因)
	 */
	public String signMsg(final String TobeSigned, final String KeyFile, final String PassWord) throws Exception
	{
		RSAPrivateCrtKey prikey = readPrivKey(KeyFile,PassWord);
		if (prikey == null)
		{
			throw new Exception("没有找到匹配私钥");
		}
		else
		{
			Signature sign = Signature.getInstance("SHA1withRSA");
			sign.initSign(prikey);
			sign.update(TobeSigned.getBytes(encoding));
			byte signed[] = sign.sign();
			byte sign_asc[] = new byte[signed.length * 2];
			Hex2Ascii(signed.length, signed, sign_asc);
			return new String(sign_asc);
		}
	}
	/**
	 * 外部指定加签算法加签
	 * @author DON
	 * @param plain
	 * @param keyfile
	 * @param password
	 * @param encoding
	 * @param algorithm
	 * @return
	 * @throws Exception
	 */
	public byte[] sign(byte[] plain, String keyfile,String password,String algorithm) throws Exception{
		RSAPrivateCrtKey prikey = readPrivKey(keyfile,password);
		if (prikey == null){
			throw new Exception("没有找到匹配私钥");
		}
		Signature sign = Signature.getInstance(algorithm);
		sign.initSign(prikey);
		sign.update(plain);
		return sign.sign();
	}
	/**
	 * 外部指定加签算法加签
	 * @author DON
	 * @param plain
	 * @param keyfile
	 * @param password
	 * @param encoding
	 * @param algorithm
	 * @return
	 * @throws Exception
	 */
	public byte[] sign(String plain, String keyfile,String password,String encoding,String algorithm) throws Exception{
		RSAPrivateCrtKey prikey = readPrivKey(keyfile,password);
		if (prikey == null){
			throw new Exception("没有找到匹配私钥");
		}
		Signature sign = Signature.getInstance(algorithm);
		sign.initSign(prikey);
		sign.update(plain.getBytes(encoding));
		return sign.sign();
	}
	/**
	 * 外部指定加签算法验签
	 * @author DON
	 * @param plain
	 * @param signed
	 * @param certFile
	 * @param encoding
	 * @param algorithm
	 * @return
	 * @throws Exception
	 */
	public boolean verify(String plain, byte[] signed, String certFile,String encoding,String algorithm) throws Exception{
		PublicKey publicKey = readPubKey(certFile);
		if (publicKey == null){
			throw new Exception("没有找到匹配公钥");
		}
		Signature sign = Signature.getInstance(algorithm);
		sign.initVerify(publicKey);
		sign.update(plain.getBytes(encoding));
        return sign.verify(signed);
	}
	/**
	 * SHA256withRSA加签
	 * @author DON
	 * @param plain
	 * @param keyfile
	 * @param password
	 * @param encoding
	 * @return
	 * @throws Exception
	 */
	public byte[] sign256(String plain, String keyfile,String password,String encoding) throws Exception{
		RSAPrivateCrtKey prikey = readPrivKey(keyfile,password);
		if (prikey == null){
			throw new Exception("没有找到匹配私钥");
		}
		Signature sign = Signature.getInstance("SHA256withRSA");
		sign.initSign(prikey);
		sign.update(plain.getBytes(encoding));
		return sign.sign();
	}
	/**
	 * SHA256withRSA验签
	 * @author DON
	 * @param plain
	 * @param signed
	 * @param certFile
	 * @param encoding
	 * @return
	 * @throws Exception
	 */
	public boolean verify256(String plain, byte[] signed, String certFile,String encoding) throws Exception{
		PublicKey publicKey = readPubKey(certFile);
		if (publicKey == null){
			throw new Exception("没有找到匹配公钥");
		}
		Signature sign = Signature.getInstance("SHA256withRSA");
		sign.initVerify(publicKey);
		sign.update(plain.getBytes(encoding));
        return sign.verify(signed);
	}

	/**
	 * 对文件进行签名
	 *
	 * @param TobeSigned
	 *            需要进行签名的文件
	 * @param KeyFile
	 *            PFX证书文件路径
	 * @param PassWord
	 *            私钥保护密码
	 * @return 签名成功返回true(从LastResult属性获取结果)，失败返回false(从LastErrMsg属性获取失败原因)
	 */
	public String signMsg(final File TobeSignedFile, final String KeyFile, final String PassWord) throws Exception
	{
		RSAPrivateCrtKey prikey = readPrivKey(KeyFile,PassWord);
		if (prikey == null)
		{
			throw new Exception("没有找到匹配私钥");
		}
		else
		{
			Signature sign = Signature.getInstance("SHA1withRSA");
			sign.initSign(prikey);
			if(!TobeSignedFile.exists())
				throw new Exception("找不到文件");
			FileInputStream fis = new FileInputStream(TobeSignedFile);
			byte[] cache = new byte[fis.available()];
			int len = fis.read(cache);
			sign.update(cache,0,len);
			fis.close();
			byte signed[] = sign.sign();
			byte sign_asc[] = new byte[signed.length * 2];
			Hex2Ascii(signed.length, signed, sign_asc);
			return new String(sign_asc);
		}
	}
	public static RSAPublicKey readPubKey(final String CertFile) throws Exception
	{
		FileInputStream certfile = null;
		RSAPublicKey pubkey=null;
		try
		{
			certfile = new FileInputStream(CertFile);
			CertificateFactory cf = CertificateFactory.getInstance("X.509");
			X509Certificate x509cert = (X509Certificate) cf.generateCertificate(certfile);
			pubkey = (RSAPublicKey) x509cert.getPublicKey();
		}
		finally
		{
			IOUtils.closeQuietly(certfile);
		}
		return pubkey;
	}
	/**
	 * 验证签名
	 *
	 * @param TobeVerified
	 *            待验证签名的密文
	 * @param PlainText
	 *            待验证签名的明文
	 * @param CertFile
	 *            签名者公钥证书
	 * @return 验证成功返回true，失败返回false(从LastErrMsg属性获取失败原因)
	 * @throws Exception
	 */
	public boolean verifyMsg(String TobeVerified, final String PlainText, final String CertFile) throws Exception
	{
		FileInputStream certfile = null;
		try
		{
			certfile = new FileInputStream(CertFile);
			return verifyMsg(TobeVerified, PlainText, certfile);
		}
		finally
		{
			IOUtils.closeQuietly(certfile);
		}
	}

	public boolean verifyMsg(String TobeVerified, final String PlainText, final InputStream CertFile) throws Exception{
		boolean result = false;
		RSAPublicKey pubkey=null;
		CertificateFactory cf = CertificateFactory.getInstance("X.509");
		X509Certificate x509cert = (X509Certificate) cf.generateCertificate(CertFile);
		pubkey = (RSAPublicKey) x509cert.getPublicKey();
		TobeVerified = TobeVerified.toLowerCase();
		Signature verify = Signature.getInstance("SHA1withRSA");
		verify.initVerify(pubkey);
		byte signeddata[] = new byte[TobeVerified.length() / 2];
		Ascii2Hex(TobeVerified.length(), TobeVerified.getBytes(encoding), signeddata);
		verify.update(PlainText.getBytes(encoding));
		if (verify.verify(signeddata))
		{
			result = true;
		}
		else
		{
			throw new Exception("验签失败");
		}
		return result;
	}

	/**
	 * 对文件验证签名
	 *
	 * @param TobeVerified
	 *            待验证签名的密文
	 * @param PlainFile
	 *            待验证签名的文件
	 * @param CertFile
	 *            签名者公钥证书
	 * @return 验证成功返回true，失败返回false(从LastErrMsg属性获取失败原因)
	 * @throws Exception
	 */
	public boolean verifyMsg(String TobeVerified, final File PlainFile, final String CertFile) throws Exception
	{
		boolean result = false;
		RSAPublicKey pubkey=readPubKey(CertFile);
		TobeVerified = TobeVerified.toLowerCase();
		Signature verify = Signature.getInstance("SHA1withRSA");
		verify.initVerify(pubkey);

		if(!PlainFile.exists())
			throw new Exception("找不到文件");
		FileInputStream fis = new FileInputStream(PlainFile);
		byte[] cache = new byte[fis.available()];
		int len = fis.read(cache);
		verify.update(cache,0,len);
		fis.close();

		byte signeddata[] = new byte[TobeVerified.length() / 2];
		Ascii2Hex(TobeVerified.length(), TobeVerified.getBytes(encoding), signeddata);

		if (verify.verify(signeddata))
		{
			result = true;
		}
		else
		{
			throw new Exception("验签失败");
		}
		return result;
	}

	/**
	 * 将十六进制数据转换成ASCII字符串
	 *
	 * @param len
	 *            十六进制数据长度
	 * @param data_in
	 *            待转换的十六进制数据
	 * @param data_out
	 *            已转换的ASCII字符串
	 */
	public static void Hex2Ascii(int len, byte data_in[], byte data_out[])
	{
		byte temp1[] = new byte[1];
		byte temp2[] = new byte[1];
		for (int i = 0, j = 0; i < len; i++)
		{
			temp1[0] = data_in[i];
			temp1[0] = (byte) (temp1[0] >>> 4);
			temp1[0] = (byte) (temp1[0] & 0x0f);
			temp2[0] = data_in[i];
			temp2[0] = (byte) (temp2[0] & 0x0f);
			if (temp1[0] >= 0x00 && temp1[0] <= 0x09)
			{
				(data_out[j]) = (byte) (temp1[0] + '0');
			}
			else if (temp1[0] >= 0x0a && temp1[0] <= 0x0f)
			{
				(data_out[j]) = (byte) (temp1[0] + 0x57);
			}

			if (temp2[0] >= 0x00 && temp2[0] <= 0x09)
			{
				(data_out[j + 1]) = (byte) (temp2[0] + '0');
			}
			else if (temp2[0] >= 0x0a && temp2[0] <= 0x0f)
			{
				(data_out[j + 1]) = (byte) (temp2[0] + 0x57);
			}
			j += 2;
		}
	}

	/**
	 * 将ASCII字符串转换成十六进制数据
	 *
	 * @param len
	 *            ASCII字符串长度
	 * @param data_in
	 *            待转换的ASCII字符串
	 * @param data_out
	 *            已转换的十六进制数据
	 */
	public static void Ascii2Hex(int len, byte data_in[], byte data_out[])
	{
		byte temp1[] = new byte[1];
		byte temp2[] = new byte[1];
		for (int i = 0, j = 0; i < len; j++)
		{
			temp1[0] = data_in[i];
			temp2[0] = data_in[i + 1];
			if (temp1[0] >= '0' && temp1[0] <= '9')
			{
				temp1[0] -= '0';
				temp1[0] = (byte) (temp1[0] << 4);

				temp1[0] = (byte) (temp1[0] & 0xf0);

			}
			else if (temp1[0] >= 'a' && temp1[0] <= 'f')
			{
				temp1[0] -= 0x57;
				temp1[0] = (byte) (temp1[0] << 4);
				temp1[0] = (byte) (temp1[0] & 0xf0);
			}

			if (temp2[0] >= '0' && temp2[0] <= '9')
			{
				temp2[0] -= '0';

				temp2[0] = (byte) (temp2[0] & 0x0f);

			}
			else if (temp2[0] >= 'a' && temp2[0] <= 'f')
			{
				temp2[0] -= 0x57;

				temp2[0] = (byte) (temp2[0] & 0x0f);
			}
			data_out[j] = (byte) (temp1[0] | temp2[0]);

			i += 2;
		}

	}
}
