package com.friday.custody.service.trustService.ebscn.utils;


import cfca.sadk.algorithm.common.Mechanism;
import cfca.sadk.algorithm.common.PKIException;
import cfca.sadk.algorithm.sm2.SM2PrivateKey;
import cfca.sadk.algorithm.sm2.SM2PublicKey;
import cfca.sadk.lib.crypto.Session;
import cfca.sadk.lib.crypto.bcsoft.BCSoftLib;
import cfca.sadk.org.bouncycastle.crypto.generators.SM2KeyPairGenerator;
import cfca.sadk.system.Mechanisms;
import cfca.sadk.util.Base64;
import cfca.sadk.util.EncryptUtil;
import cfca.sadk.util.KeyUtil;
import cfca.sadk.util.P12FileKit;
import cfca.sadk.x509.certificate.X509Cert;
import cfca.sadk.x509.certificate.X509CertGenerator;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.Feature;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.*;

/**
 * <pre>
 * 摘  要：国密算法
 *
 * 作  者：吴丰辉(wufh@ebscn.com)
 * 时  间：2021/12/27 16:48
 *
 * Copyright © 版权2021 光大证券股份有限公司
 * </pre>
 **/
public class SM2Utils {
    /**
     * 密码字符串
     */
    public static final char[] PASS_CHARS = "ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789!@#$%^&*".toCharArray();
    /**
     * 证书颁发者
     */
    public static final String CERT_ISSUER = "C = CN, ST = Shanghai, L = Shanghai, O = EBSCN, OU = \"Everbright Securities Co,. Ltd.\", " +
            "CN = www.ebscn.com, emailAddress = wufh@ebscn.com";
    /**
     * 生成工厂
     */
    public static final Session SM_SESSION = BCSoftLib.INSTANCE();
    /**
     * SM2加密/解密测试机制
     */
    public static final Mechanism MECHANISM_SM2 = Mechanisms.K_SM2;
    /**
     * SM2签名
     */
    public static final Mechanism MECHANISM_SIGN = Mechanisms.M_SM3_SM2;

    /**
     * SM2非对称加密公钥和私钥
     *
     * @return 公钥私钥对
     */
    public static KeyPair generateKeyPair4SM2() {
        return SM2KeyPairGenerator.SM2GenerateKeyPair();
    }

    /**
     * 生成SM2私钥
     *
     * @return SM私钥
     */
    public static SM2PrivateKey genSm2PrivateKey() throws PKIException {
        KeyPair keyPair = generateKeyPair4SM2();
        return KeyUtil.getSM2PrivateKey(keyPair.getPrivate().getEncoded());
    }

    /**
     * SM2非对称加密公钥和私钥
     *
     * @return 公钥私钥对
     */
    public static Map<String, String> genSM2KeyPair(KeyPair keyPair) {
        if (null != keyPair) {
            Map<String, String> keyPairMap = new HashMap<>();
            keyPairMap.put("pub_key", Base64.toBase64String(keyPair.getPublic().getEncoded()));
            keyPairMap.put("pri_key", Base64.toBase64String(keyPair.getPrivate().getEncoded()));
            return keyPairMap;
        }
        return null;
    }

    /**
     * SM2非对称加密公钥和私钥
     *
     * @return 公钥私钥对
     */
    public static Map<String, String> genSM2KeyPair() {
        return genSM2KeyPair(generateKeyPair4SM2());
    }

    /**
     * 根据SM2私钥生成证书
     *
     * @param sm2PrivateKey SM2密钥
     * @return 证书字节码
     */
    public static byte[] genCert(SM2PrivateKey sm2PrivateKey, String subject) throws Exception {
        X509CertGenerator certGen = new X509CertGenerator();
        certGen.setSerialNumber(randomUUID());
        certGen.setIssuer(CERT_ISSUER);
        certGen.setSubject(subject);
        certGen.setPublicKey(sm2PrivateKey.getSM2PublicKey());
        certGen.setSignatureAlg(MECHANISM_SIGN.getMechanismType());
        Date now = new Date();
        certGen.setNotBefore(now);
        certGen.setNotAfter(DateUtils.addDays(now, 365 * 10));
        return certGen.generateX509Cert(sm2PrivateKey, SM_SESSION);
    }

    /**
     * 生成sm2文件
     *
     * @param sm2PrivateKey SM2私钥
     * @param subject       颁发给
     * @param password      密码
     * @return 文件流
     * @throws Exception 异常
     */
    public static byte[] genSM2File(SM2PrivateKey sm2PrivateKey, String subject, String password) throws Exception {
        // 文件流
        X509Cert x509Cert = new X509Cert(genCert(sm2PrivateKey, subject));
        return P12FileKit.SM2CombineP12Data(x509Cert, sm2PrivateKey, password);
    }

    /**
     * 从base64字符串中获取SM2公钥
     *
     * @param base64String 公钥字符串
     * @return SM2公钥
     * @throws PKIException 解析异常
     */
    public static SM2PublicKey getSM2PublicKey(String base64String) throws Exception {
        return KeyUtil.getSM2PublicKey(Base64.decode(base64String));
    }

    /**
     * 从base64字符串中获取SM2私钥
     *
     * @param base64String 私钥字符串
     * @return SM2私钥
     * @throws PKIException 解析异常
     */
    public static SM2PrivateKey getSM2PrivateKey(String base64String) throws Exception {
        return KeyUtil.getSM2PrivateKey(Base64.decode(base64String));
    }

    /**
     * 从证书中获得SM2私钥
     *
     * @param SM2CertPath SM2证书位置
     * @param secret      客户端密钥
     * @return 私钥
     */
    public static SM2PrivateKey getSM2PrivateKeyFromCert(String SM2CertPath, String secret) {
        try {
            return KeyUtil.getPrivateKeyFromSM2(SM2CertPath, secret);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * SM2签名
     *
     * @param srcData 原始数据
     * @param priKey  私钥
     * @return 签名
     * @throws PKIException 异常
     */
    public static byte[] sign(byte[] srcData, PrivateKey priKey) throws Exception {
        return SM_SESSION.sign(MECHANISM_SIGN, priKey, srcData);
    }

    /**
     * SM2签名
     *
     * @param srcStr      原始字符串
     * @param priKey      私钥
     * @param charsetName 字符集
     * @return 签名
     * @throws PKIException 异常
     */
    public static String sign(String srcStr, PrivateKey priKey, String charsetName) throws Exception {
        return Base64.toBase64String(sign(srcStr.getBytes(charsetName), priKey));
    }

    /**
     * SM2签名
     *
     * @param srcStr 原始字符串
     * @param priKey 私钥
     * @return 签名
     * @throws PKIException 异常
     */
    public static String sign(String srcStr, PrivateKey priKey) throws Exception {
        return sign(srcStr, priKey, "UTF-8");
    }

    /**
     * SM2签名
     *
     * @param srcStr 原始字符串
     * @param priKey 私钥
     * @return 签名
     * @throws PKIException 异常
     */
    public static String sign(String srcStr, String priKey) throws Exception {
        return sign(srcStr, getSM2PrivateKey(priKey), "UTF-8");
    }

    /**
     * SM2签名验证
     *
     * @param srcData 原始数据
     * @param pubKey  公钥
     * @return 签名是否通过
     * @throws PKIException 异常
     */
    public static boolean verify(byte[] srcData, byte[] signData, PublicKey pubKey) throws PKIException {
        return SM_SESSION.verify(MECHANISM_SIGN, pubKey, srcData, signData);
    }

    /**
     * SM2签名验证
     *
     * @param srcData 原始数据
     * @param pubKey  公钥
     * @return 签名是否通过
     * @throws PKIException 异常
     */
    public static boolean verify(String srcData, String signData, PublicKey pubKey, String charsetName) throws Exception {
        return verify(srcData.getBytes(charsetName), Base64.decode(signData), pubKey);
    }

    /**
     * SM2签名验证
     *
     * @param srcData 原始数据
     * @param pubKey  公钥
     * @return 签名是否通过
     * @throws PKIException 异常
     */
    public static boolean verify(String srcData, String signData, String pubKey) throws Exception {
        return verify(srcData, signData, getSM2PublicKey(pubKey), "UTF-8");
    }

    /**
     * SM2加密
     *
     * @param srcData 原始数据
     * @param pubKey  公钥
     * @return 加密后的数据
     * @throws PKIException 异常
     */
    public static byte[] encrypt4SM2(byte[] srcData, PublicKey pubKey) throws PKIException {
        return EncryptUtil.encrypt(MECHANISM_SM2, pubKey, srcData, SM_SESSION);
    }

    /**
     * SM2加密字符串
     *
     * @param srcStr 原始字符串
     * @param pubKey 公钥
     * @return 加密后的字符串
     * @throws Exception 异常
     */
    public static String encrypt4SM2(String srcStr, PublicKey pubKey) throws Exception {
        byte[] encryptBytes = encrypt4SM2(srcStr.getBytes(StandardCharsets.UTF_8), pubKey);
        return new String(encryptBytes, StandardCharsets.UTF_8);
    }

    /**
     * SM2加密字符串
     *
     * @param srcStr      原始字符串
     * @param pubKey      公钥
     * @param charsetName 字符集
     * @return 加密后的字符串
     * @throws Exception 异常
     */
    public static String encrypt4SM2(String srcStr, String pubKey, String charsetName) throws Exception {
        byte[] encryptBytes = encrypt4SM2(srcStr.getBytes(charsetName), getSM2PublicKey(pubKey));
        return new String(encryptBytes, charsetName);
    }

    /**
     * SM2加密字符串
     *
     * @param srcStr 原始字符串
     * @param pubKey 公钥
     * @return 加密后的字符串
     * @throws Exception 异常
     */
    public static String encrypt4SM2(String srcStr, String pubKey) throws Exception {
        return encrypt4SM2(srcStr, pubKey, "UTF-8");
    }

    /**
     * SM2加密字符串
     *
     * @param encData 加密的数据
     * @param priKey  私钥
     * @return 解密后的字符串
     * @throws Exception 异常
     */
    public static byte[] decrypt4SM2(byte[] encData, PrivateKey priKey) throws Exception {
        return EncryptUtil.decrypt(MECHANISM_SM2, priKey, encData, SM_SESSION);
    }

    /**
     * SM2加密字符串
     *
     * @param encStr 加密的字符串
     * @param priKey 私钥
     * @return 解密后的字符串
     * @throws Exception 异常
     */
    public static String decrypt4SM2(String encStr, PrivateKey priKey, String charsetName) throws Exception {
        return new String(decrypt4SM2(encStr.getBytes(charsetName), priKey), charsetName);
    }

    /**
     * SM2加密字符串
     *
     * @param encStr 加密的字符串
     * @param priKey 私钥
     * @return 解密后的字符串
     * @throws Exception 异常
     */
    public static String decrypt4SM2(String encStr, PrivateKey priKey) throws Exception {
        return decrypt4SM2(encStr, priKey, "UTF-8");
    }

    /**
     * SM2加密字符串
     *
     * @param encStr      加密的字符串
     * @param priKey      私钥
     * @param charsetName 字符集
     * @return 解密后的字符串
     * @throws Exception 异常
     */
    public static String decrypt4SM2(String encStr, String priKey, String charsetName) throws Exception {
        byte[] decryptBytes = decrypt4SM2(encStr.getBytes(charsetName), getSM2PrivateKey(priKey));
        return new String(decryptBytes, charsetName);
    }

    /**
     * SM2加密字符串
     *
     * @param encStr 加密的字符串
     * @param priKey 私钥
     * @return 解密后的字符串
     * @throws Exception 异常
     */
    public static String decrypt4SM2(String encStr, String priKey) throws Exception {
        return decrypt4SM2(encStr, priKey, "UTF-8");
    }

    /**
     * 生成UUID
     *
     * @return uuid
     */
    public static String randomUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    /**
     * 加密业务数据
     *
     * @param bizData       业务数据
     * @param secret        密钥
     * @param sm2PrivateKey 私钥
     * @return 加密后的业务数据
     */
    public static Map<String, Object> encryptBizData(Map<String, Object> bizData,
                                                     String secret, SM2PrivateKey sm2PrivateKey) throws Exception {
        return encryptBizData(JSON.toJSONString(bizData), secret, sm2PrivateKey);
    }

    /**
     * 加密业务数据
     *
     * @param bizDataStr    业务数据字符串
     * @param secret        密钥
     * @param sm2PrivateKey 私钥
     * @return 加密后的业务数据
     */
    public static Map<String, Object> encryptBizData(String bizDataStr,
                                                     String secret, SM2PrivateKey sm2PrivateKey) throws Exception {
        // 时间戳
        String timestamp = String.valueOf((new Date()).getTime());
        // 签名内容
        String content4Sign = StringUtils.join(new String[]{secret, timestamp, bizDataStr}, "|");
        // 签名
        String sign = SM2Utils.sign(content4Sign, sm2PrivateKey);

        // 组装新的参数
        Map<String, Object> reqMap = new HashMap<>(3);
        reqMap.put("timestamp", timestamp);
        // 加密原始请求内容
        reqMap.put("biz_data", SM2Utils.encrypt4SM2(bizDataStr, sm2PrivateKey.getSM2PublicKey()));
        reqMap.put("sign", sign);

        return reqMap;
    }

    /**
     * 业务数据SM2签名验证及解密
     *
     * @param bizDataEncStr 加密的业务数据
     * @param sign          签名
     * @param timestamp     时间戳
     * @param secret        密钥
     * @param sm2PrivateKey SM2私钥
     * @return 解密后的业务数据串
     */
    public static String verifyAndDecryptBizData(String bizDataEncStr, String sign, String timestamp,
                                                 String secret, SM2PrivateKey sm2PrivateKey) {
        try {
            // 解密业务参数
            String bizDataDecStr = SM2Utils.decrypt4SM2(bizDataEncStr, sm2PrivateKey);
            // System.out.println(bizDataDecStr);
            // 验证签名
            String[] signContent = new String[]{secret, timestamp, bizDataDecStr};
            boolean isValid = SM2Utils.verify(StringUtils.join(signContent, "|"),
                    sign, sm2PrivateKey.getSM2PublicKey(), "UTF-8");
            if (isValid) {
                return bizDataDecStr;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 业务数据SM2签名验证及解密
     *
     * @param bizData       业务数据
     * @param secret        密钥
     * @param sm2PrivateKey SM2私钥
     * @return 解密后的业务数据串
     */
    public static Map<String, Object> verifyAndDecryptBizData(Map<String, Object> bizData,
                                                              String secret, SM2PrivateKey sm2PrivateKey) {
        // 有签名字段时需要校验和解密
        if (bizData.containsKey("sign")) {
            return JSON.parseObject(verifyAndDecryptBizData(
                    ObjectUtils.toString(bizData.get("biz_data")),
                    ObjectUtils.toString(bizData.get("sign")),
                    ObjectUtils.toString(bizData.get("timestamp")),
                    secret, sm2PrivateKey),
                    new TypeReference<LinkedHashMap<String, Object>>() {}, Feature.OrderedField);
        } else {
            return bizData;
        }
    }
}

