package cn.wisenergy.common.utils;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.util.Assert;

/**
 * Utils - RSA加密解密
 *
 * @version 3.0
 */
public final class RSAUtil {

	/**
	 * 安全服务提供者
	 */
	private static final Provider PROVIDER = new BouncyCastleProvider();

	/**
	 * 密钥大小
	 */
	private static final int KEY_SIZE = 1024;

	/**
	 * 加密解密模式 - 填充
	 */
	public static final String RSA_ECB_PKCS1PADDING = "RSA/ECB/PKCS1Padding";

	/**
	 * 加密解密模式
	 */
	public static final String RSA = "RSA";

	/**
	 * 不可实例化
	 */
	private RSAUtil() {
	}

	/**
	 * 生成密钥对
	 *
	 * @return 密钥对
	 */
	public static KeyPair generateKeyPair() {
		try {
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
					"RSA", PROVIDER);
			keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
			return keyPairGenerator.generateKeyPair();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 通过公钥和数组加密
	 *
	 * @param publicKey
	 *            公钥
	 * @param data
	 *            数据
	 * @return 加密后的数据
	 */
	@SuppressWarnings("deprecation")
	public static byte[] encrypt(PublicKey publicKey, byte[] data) {
		Assert.notNull(publicKey);
		Assert.notNull(data);
		try {
			Cipher cipher = Cipher.getInstance("RSA", PROVIDER);
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			int blockSize = cipher.getBlockSize();// 获得加密块大小，如：加密前数据为128个byte，而key_size=1024
			// 加密块大小为127byte,加密后为128个byte;因此共有2个加密块，第一个127byte第二个为1个byte
			int outputSize = cipher.getOutputSize(data.length);// 获得加密块加密后块大小
			int leavedSize = data.length % blockSize;
			int blocksSize = leavedSize != 0 ? data.length / blockSize + 1
					: data.length / blockSize;
			byte[] raw = new byte[outputSize * blocksSize];
			int i = 0;
			while (data.length - i * blockSize > 0) {
				if (data.length - i * blockSize > blockSize) {
					cipher.doFinal(data, i * blockSize, blockSize, raw, i
							* outputSize);
				} else {
					cipher.doFinal(data, i * blockSize, data.length - i
							* blockSize, raw, i * outputSize);
				}
				i++;
			}
			return raw;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 通过公钥和字符串加密
	 *
	 * @param publicKey
	 *            公钥
	 * @param text
	 *            字符串
	 * @return Base64编码字符串
	 */
	@SuppressWarnings("deprecation")
	public static String encrypt(PublicKey publicKey, String text) {
		Assert.notNull(publicKey);
		Assert.notNull(text);
		byte[] data = encrypt(publicKey, text.getBytes());
		return data != null ? Base64.encodeBase64String(data) : null;
	}

	/**
	 * 通过公钥的模量和指数构建公钥后加密字符串
	 *
	 * @param modulus
	 * @param exponent
	 * @param text
	 * @return
	 */
	public static String encrypt(String modulus, String exponent, String text) {
		try {
			byte[] aryExponent = Base64.decodeBase64(exponent);
			byte[] aryModulus = Base64.decodeBase64(modulus);
			BigInteger bigExponent = new BigInteger(1, aryExponent);
			BigInteger bigModulus = new BigInteger(1, aryModulus);
			RSAPublicKeySpec keySpec = new RSAPublicKeySpec(bigModulus,
					bigExponent);
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			PublicKey publicKey = keyFactory.generatePublic(keySpec);
			return encrypt(publicKey, text);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 通过私钥和数组解密
	 *
	 * @param privateKey
	 *            私钥
	 * @param data
	 *            数据
	 * @return 解密后的数据
	 */
	@SuppressWarnings({ "deprecation", "static-access" })
	public static byte[] decrypt(PrivateKey privateKey, byte[] data) {
		Assert.notNull(privateKey);
		Assert.notNull(data);
		try {
			Cipher cipher = Cipher.getInstance("RSA", PROVIDER);
			cipher.init(cipher.DECRYPT_MODE, privateKey);
			int blockSize = cipher.getBlockSize();
			ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
			int j = 0;

			while (data.length - j * blockSize > 0) {
				bout.write(cipher.doFinal(data, j * blockSize, blockSize));
				j++;
			}
			return bout.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 通过私钥和数组解密 - 有模式
	 *
	 * @param mode
	 *            模式
	 * @param privateKey
	 *            私钥
	 * @param data
	 *            数据
	 * @return 解密后的数据
	 */
	@SuppressWarnings({ "deprecation", "static-access" })
	public static byte[] decrypt(String mode, PrivateKey privateKey, byte[] data) {
		Assert.notNull(privateKey);
		Assert.notNull(data);
		try {
			Cipher cipher = Cipher.getInstance(mode, PROVIDER);
			cipher.init(cipher.DECRYPT_MODE, privateKey);
			int blockSize = cipher.getBlockSize();
			ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
			int j = 0;

			while (data.length - j * blockSize > 0) {
				bout.write(cipher.doFinal(data, j * blockSize, blockSize));
				j++;
			}
			return bout.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 通过私钥和数组解密 - 点位图用
	 *
	 * @param privateKey
	 *            私钥
	 * @param data
	 *            数据
	 * @return 解密后的数据
	 */
	@SuppressWarnings({ "deprecation", "static-access" })
	public static byte[] decryptForBitmap(PrivateKey privateKey, byte[] data) {
		Assert.notNull(privateKey);
		Assert.notNull(data);
		try {
			// 点位图ios端使用了默认的填充模式: RSA/ECB/PKCS1Padding
			Cipher cipher = Cipher
					.getInstance("RSA/ECB/PKCS1Padding", PROVIDER);
			cipher.init(cipher.DECRYPT_MODE, privateKey);
			int blockSize = cipher.getBlockSize();
			ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
			int j = 0;

			while (data.length - j * blockSize > 0) {
				bout.write(cipher.doFinal(data, j * blockSize, blockSize));
				j++;
			}
			return bout.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 通过私钥和字符串解密
	 *
	 * @param privateKey
	 *            私钥
	 * @param text
	 *            Base64编码字符串
	 * @return 解密后的数据
	 */
	@SuppressWarnings("deprecation")
	public static String decrypt(PrivateKey privateKey, String text) {
		Assert.notNull(privateKey);
		Assert.notNull(text);
		byte[] data = decrypt(privateKey, Base64.decodeBase64(text));
		return data != null ? new String(data) : null;
	}

	/**
	 * 通过私钥和字符串解密 - 有模式
	 *
	 * @param mode
	 *            模式
	 * @param privateKey
	 *            私钥
	 * @param text
	 *            Base64编码字符串
	 * @return 解密后的数据
	 */
	@SuppressWarnings("deprecation")
	public static String decrypt(String mode, PrivateKey privateKey, String text) {
		Assert.notNull(privateKey);
		Assert.notNull(text);
		byte[] data = decrypt(mode, privateKey, Base64.decodeBase64(text));
		return data != null ? new String(data) : null;
	}

	/**
	 * 通过私钥和字符串解密 - 点位图
	 *
	 * @param privateKey
	 *            私钥
	 * @param text
	 *            Base64编码字符串
	 * @return 解密后的数据
	 */
	@SuppressWarnings("deprecation")
	public static String decryptForBitmap(PrivateKey privateKey, String text) {
		Assert.notNull(privateKey);
		Assert.notNull(text);
		byte[] data = decryptForBitmap(privateKey, Base64.decodeBase64(text));
		return data != null ? new String(data) : null;
	}

	/**
	 * 通过私钥的模量和指数构建私钥后和字符串解密
	 *
	 * @param modulus
	 * @param exponent
	 * @param text
	 * @return
	 */
	public static String decrypt(String modulus, String exponent, String text) {
		try {
			byte[] aryExponent = Base64.decodeBase64(exponent);
			byte[] aryModulus = Base64.decodeBase64(modulus);
			BigInteger bigExponent = new BigInteger(1, aryExponent);
			BigInteger bigModulus = new BigInteger(1, aryModulus);
			RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(bigModulus,
					bigExponent);
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
			return decrypt(privateKey, text);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public static void main(String[] args) {
		String exponent = "AQAB";
		String modulus = "AKJ7e1Lhn5kBnQQ++UWl8MJn9dGwCoTdXExlOsI6YVZkq4+R+Qb3gNo5v1TgHgfS2EMQ0YChr2//nJdmKc1w8bAz6XPRD4L2ZXnKDTfTOmKcel1jC7CzNUY5M1ahhEQeI6f367loH2me9UwScBN8rtIeGEGhP8E8DVriTk9g1xFv";
		String password = "admin123";
		System.out.print(encrypt(modulus, exponent, password));
	}
}