package com.qunzhixinxi.hnqz.admin.util;

import cn.hutool.json.JSONUtil;
import com.qunzhixinxi.hnqz.admin.entity.dto.EncryptedInput;

import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * {@code RSAUtils} 非对称加密工具类
 * 如果客户端和服务端通信，既要实现RSA的加解密，又要实现RSA的签名验签，那么就需要两对RSA密钥。
 * 客户端发送请求：用服务端的公钥进行加密数据，用客户端的私钥进行数据签名。
 * 服务器收到请求：用客户端的公钥进行验签，用服务端的私钥进行解密。
 *
 * 服务端发送响应：用客户端的公钥进行加密数据，用服务端的私钥进行数据签名。
 * 客户端收到响应：用服务端的公钥进行验签，用客户端的私钥进行解密数据。
 *
 * @author Hengchen.Sun
 * @version 1.0.0
 * @date 2021-04-06
 * @since ver.1.0.0
 */
public final class RSAUtils {

	private RSAUtils() {
	}

	//取值 SHA256WithRSA  或者 SHA1WithRSA
	//RSA 对应 SHA1WithRSA
	//RSA2 对应 SHA256WithRSA  至少2048 位以上
	public static final String SHA_WITH_RSA_ALGORITHM = "SHA256WithRSA";

	public static final String MD5_WITH_RSA = "MD5WithRSA";

	public static final int KEY_SIZE_2048 = 2048;

	public static final int KEY_SIZE_1024 = 1024;

	public static final String ALGORITHM = "RSA";

	public static final String CHARSET = "UTF-8";

	public static final String RSA_PUBLIC_KEY = "rsaPublicKey";

	public static final String RSA_PRIVATE_KEY = "rsaPrivateKey";


	/**
	 * SHAWithRSA签名
	 *
	 * @param signStr    签名字符串
	 * @param privateKey 私钥
	 * @param algorithm  算法
	 * @return
	 * @throws Exception
	 */
	public static String sign(final String signStr, final String privateKey, final String algorithm) throws Exception {
		PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
		KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
		PrivateKey priKey = keyFactory.generatePrivate(priPKCS8);
		Signature signature = Signature.getInstance(algorithm != null ? algorithm : SHA_WITH_RSA_ALGORITHM);
		signature.initSign(priKey);
		signature.update(signStr.getBytes());
		byte[] signatureData = signature.sign();
		return Base64.getEncoder().encodeToString(signatureData);
	}

	/**
	 * SHAWithRSA签名
	 * 默认 SHA256WithRSA 签名
	 *
	 * @param signStr    签名字符串
	 * @param privateKey 私钥
	 * @return 签名后的字符串
	 * @throws Exception
	 */
	public static String sign(final String signStr, final String privateKey) throws Exception {
		return sign(signStr, privateKey, null);
	}

	/**
	 * SHAWithRSA验签
	 * 默认SHAWithRSA验签
	 *
	 * @param signStr   签名字符串
	 * @param signData  签名的信息
	 * @param publicKey 公钥
	 * @param algorithm 算法
	 * @return 验签结果
	 * @throws Exception
	 */
	public static boolean isValid(final String signStr, final String signData, final String publicKey, final String algorithm) throws Exception {
		KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
		byte[] encodedKey = Base64.getDecoder().decode(publicKey);
		PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
		Signature signa = Signature.getInstance(algorithm != null ? algorithm : SHA_WITH_RSA_ALGORITHM);
		signa.initVerify(pubKey);
		signa.update(signStr.getBytes());
		byte[] sign_byte = Base64.getDecoder().decode(signData);
		boolean flag = signa.verify(sign_byte);
		return flag;
	}

	/**
	 * SHAWithRSA验签
	 *
	 * @param signStr   签名字符串
	 * @param signData  签名信息
	 * @param publicKey 公钥
	 * @return 验签结果
	 * @throws Exception
	 */
	public static boolean isValid(final String signStr, final String signData, final String publicKey) throws Exception {
		return isValid(signStr, signData, publicKey, null);
	}

	/**
	 * RSA公钥加密
	 *
	 * @param content   明文信息
	 * @param publicKey 公钥
	 * @return 加密后的字符串
	 * @throws Exception
	 */
	public static String encrypt(final String content, final String publicKey) throws Exception {
		//base64编码的公钥
		byte[] decoded = Base64.getDecoder().decode(publicKey);
		RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
		//RSA加密
		Cipher cipher = Cipher.getInstance(ALGORITHM);
		cipher.init(Cipher.ENCRYPT_MODE, pubKey);
		String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(content.getBytes(CHARSET)));
		return outStr;
	}

	/**
	 * RSA私钥解密
	 *
	 * @param encryptedData 密文
	 * @param privateKey    私钥
	 * @return 解密后的内容
	 * @throws Exception
	 */
	public static String decrypt(final String encryptedData, final String privateKey) throws Exception {
		//64位解码加密后的字符串
		byte[] inputByte = java.util.Base64.getDecoder().decode(encryptedData.getBytes(CHARSET));
		//base64编码的私钥
		byte[] decoded = Base64.getDecoder().decode(privateKey);
		RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
		//RSA解密
		Cipher cipher = Cipher.getInstance(ALGORITHM);
		cipher.init(Cipher.DECRYPT_MODE, priKey);
		String outStr = new String(cipher.doFinal(inputByte));
		return outStr;
	}

	/**
	 * 随机生成密钥对
	 *
	 * @param keySize key大小，默认为2048位
	 * @return 公钥私钥对
	 * @throws NoSuchAlgorithmException
	 */
	public static Map<String, String> genKeyPair(Integer keySize) throws NoSuchAlgorithmException {
		//推荐 2048位以上，保证安全性
		if (keySize == null) {
			keySize = KEY_SIZE_2048;
		}
		// KeyPairGenerator类用于生成公钥和私钥对，基于RSA算法生成对象
		KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM);
		// 初始化密钥对生成器，密钥大小为96-1024位
		keyPairGen.initialize(keySize, new SecureRandom());
		// 生成一个密钥对，保存在keyPair中
		KeyPair keyPair = keyPairGen.generateKeyPair();
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
		// 得到公钥，私钥字符串
		String publicKeyString = new String(Base64.getEncoder().encode(publicKey.getEncoded()));
		String privateKeyString = new String(Base64.getEncoder().encode(privateKey.getEncoded()));
		// 将公钥和私钥保存到Map
		Map<String, String> keyPairMap = new HashMap<>(2);
		keyPairMap.put(RSA_PUBLIC_KEY, publicKeyString);
		keyPairMap.put(RSA_PRIVATE_KEY, privateKeyString);
		return keyPairMap;
	}
}
