package com.xunmei.common.security.utils;


import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.io.resource.NoResourceException;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;



@Slf4j
public class AsymmetricEncryptionUtil {

    private static final String RSA = "RSA";
    private static final ConcurrentHashMap<String,Key> cache = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        //test("123456");
        String content = "GhZ/K5X9m/c2ArlDvH1H2IU0TOfAV0mR7vZJxXtanaS0GyNRPu/AzQld9Oe6LmaJRRSEleJQ6041u6IqeGKXnqsjrK1IQjwtJDgTAz3GvbxyOsedl0pol2FqdvQFw/y3rsFEFQsCYPPF7IYS/6YScSS+F7Qm/k+6fYryJG1xHoU=";
        String decrypt = decrypt(content);
        System.out.println("解密后明文为:"+decrypt);
    }

    public static void test(String content) {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
            keyPairGenerator.initialize(1024);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            //生成公钥
            RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
            //生成密钥
            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
            //加密
            String encrypt = encryptByAsymmetric(content, rsaPublicKey);
            System.out.println("加密密文：" + encrypt);
            //解密
            String decrypt = decryptByAsymmetric(encrypt, rsaPrivateKey);
            System.out.println("原文内容：" + content);
            System.out.println("解密明文：" + decrypt);
        } catch (NoSuchAlgorithmException e) {
            log.error("加解密失败", e);
        }
    }


    /**
     * 从文件中加载密钥
     */
    private static Key loadKeyFromFile(InputStream inputStream, Boolean isPublic) {

        try {
            //将InputStream读入Reader中
            Reader reader = new InputStreamReader(inputStream);
            //实例化一个StringBuilder以保存结果
            StringBuilder result = new StringBuilder();
            //读取每个字节并转换为char，添加到StringBuilder
            for (int data = reader.read(); data != -1; data = reader.read()) {
                result.append((char) data);
            }
            // 将文件内容转为字符串
            String keyString = result.toString();
            //String keyString = FileUtils.readFileToString(file, String.valueOf(StandardCharsets.UTF_8));
            // 进行Base64解码
            byte[] decode = cn.hutool.core.codec.Base64.decode(keyString);
            // 获取密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            if (!isPublic) {
                // 构建密钥规范
                PKCS8EncodedKeySpec key = new PKCS8EncodedKeySpec(decode);
                return keyFactory.generatePrivate(key);
            }
            // 构建密钥规范
            X509EncodedKeySpec key = new X509EncodedKeySpec(decode);
            // 获取公钥
            return keyFactory.generatePublic(key);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("获取密钥文件失败！");
        }
    }

    public static String encrypt(String content) {
        if (ObjectUtil.isEmpty(content)) {
            return null;
        }
        if (cache.containsKey("publicKey")){
            return encryptByAsymmetric(content, (RSAPublicKey) cache.get("publicKey"));
        }
        String result = null;
        try {
            final ClassPathResource resource = new ClassPathResource("key/publicKey.pub");
            RSAPublicKey publicKey1 = (RSAPublicKey) loadKeyFromFile(resource.getStream(), true);
            cache.put("publicKey",publicKey1);
            result = encryptByAsymmetric(content, publicKey1);
        } catch (Exception e) {
            log.error("加密失败", e);
        }
        return result;
    }

    public static String decrypt(String encrypt) {
        if (ObjectUtil.isEmpty(encrypt)) {
            return null;
        }
        if (cache.containsKey("privateKey")){
            return decryptByAsymmetric(encrypt, (RSAPrivateKey) cache.get("privateKey"));
        }
        String result = null;
        try {
            ClassPathResource resource = new ClassPathResource("key/private.pri");
            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) loadKeyFromFile(resource.getStream(), false);
            cache.put("privateKey",rsaPrivateKey);
            result = decryptByAsymmetric(encrypt, rsaPrivateKey);
        } catch (NoResourceException e) {
            log.error("解密失败", e);
        }
        return result;
    }

    /**
     * 解密
     *
     * @param encrypted : 密文
     * @param key       : 密钥
     * @return : 原文
     * @throws Exception
     */
    private static String decryptByAsymmetric(String encrypted, Key key) {
        try {
            // 获取Cipher对象
            Cipher cipher = Cipher.getInstance(RSA);
            // 初始化模式(解密)和密钥
            cipher.init(Cipher.DECRYPT_MODE, key);
            return new String(getMaxResultDecrypt(encrypted, cipher));
        } catch (
                Exception e) {
            e.printStackTrace();
            throw new RuntimeException("解密失败！");
        }
    }


    /**
     * 加密
     *
     * @param content : 加密内容
     * @param key     : 密钥(公钥/密钥)
     * @return : 密文
     * @throws Exception
     */
    private static String encryptByAsymmetric(String content, Key key) {
        try {
            // 获取Cipher对象
            Cipher cipher = Cipher.getInstance(RSA);
            // 初始化模式(加密)和密钥
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] resultBytes = getMaxResultEncrypt(content, cipher);
            return Base64.encodeBase64String(resultBytes);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("加密失败！");
        }
    }

    /**
     * 分段处理加密数据
     *
     * @param content : 加密文本
     * @param cipher  : Cipher对象
     * @return
     */
    private static byte[] getMaxResultEncrypt(String content, Cipher cipher) throws Exception {
        byte[] inputArray = content.getBytes();
        int inputLength = inputArray.length;
        // 最大加密字节数，超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 117;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

    /**
     * 分段处理解密数据
     *
     * @param decryptText : 加密文本
     * @param cipher      : Cipher对象
     * @throws Exception
     */
    private static byte[] getMaxResultDecrypt(String decryptText, Cipher cipher) throws Exception {
        byte[] inputArray = Base64.decodeBase64(decryptText.getBytes(StandardCharsets.UTF_8));
        int inputLength = inputArray.length;

        // 最大解密字节数，超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 128;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }


}

