/*
 * SM2Util.java
 */
package com.ruoyi.common.third.utils;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 类的用途：SM2加密解密工具类，按照新标准C1C3C2顺序排放<p>
 * @version 1.0
 */
public class SM2Util {

    public static final String PRIKEY = "priKey";

    public static final String PUBKEY = "pubKey";

    private static SM2 sm2 = SM2.Instance();

    /**
     * . 生成随机密钥对
     * generateKeyPair
     * @author: WangBinBin
     * @return 密钥对
     */
    public static final Map<String, String> generateKeyPair() {
        Map<String, String> keyPairMap = new HashMap<String, String>();
        AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
        BigInteger privateKey = ecpriv.getD();
        ECPoint publicKey = ecpub.getQ();

        byte[] priTmp = privateKey.toByteArray();
        byte[] pri = new byte[32];
        if (priTmp.length == 33) {
            System.arraycopy(priTmp, 1, pri, 0, 32);
        }

        if (priTmp.length == 32) {
            System.arraycopy(priTmp, 0, pri, 0, 32);
        }

        if (priTmp.length == 31) {
            System.arraycopy(priTmp, 0, pri, 1, 31);
        }
        keyPairMap.put(PRIKEY, CipherUtil.byteToHex(pri));
        keyPairMap.put(PUBKEY, CipherUtil.byteToHex(publicKey.getEncoded(false)));
        return keyPairMap;
    }

    /**
     * @category公钥对数据加密
     * @param data publicKey公钥
     * @param data 需要加密数据
     * @return byte[] 加密结果
     * */
    private static String encrypt(byte[] publicKey, byte[] data) throws IOException {
        if (publicKey == null || publicKey.length != 65) {
            return null;
        }

        if (data == null || data.length == 0) {
            return null;
        }

        byte[] source = new byte[data.length];
        System.arraycopy(data, 0, source, 0, data.length);

        CipherUtil cipher = new CipherUtil();

        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey).normalize();

        ECPoint c1 = cipher.Init_enc(sm2, userKey).normalize();
        cipher.Encrypt(source);
        byte[] c3 = new byte[32];
        cipher.Dofinal(c3);
        //c1.getEncoded(false) 压缩格式 true压缩，false非压缩，低版本默认非压缩
        return String.valueOf(
                CipherUtil.byteToHex(c1.getEncoded(false)) + CipherUtil.byteToHex(c3) + CipherUtil.byteToHex(source));
    }

    /**
     * 私钥解密
     * decrypt
     * @author: WangBinBin
     * @param privateKey
     * @param encryptedData
     * @return
     * @throws IOException
     */
    private static byte[] decrypt(byte[] privateKey, byte[] encryptedData) throws IOException {
        if (privateKey == null || privateKey.length != 32) {
            return null;
        }

        if (encryptedData == null || encryptedData.length < 97) {
            return null;
        }

        String data = CipherUtil.byteToHex(encryptedData);
        final byte[] c1Bytes = CipherUtil.hexToByte(data.substring(0, 130));
        final int c2Len = encryptedData.length - 97;
        final byte[] c2 = CipherUtil.hexToByte(data.substring(194, 194 + 2 * c2Len));
        final byte[] c3 = CipherUtil.hexToByte(data.substring(130, 194));
        final byte[] tc3 = CipherUtil.hexToByte(data.substring(130, 194));

        final BigInteger userD = new BigInteger(1, privateKey);
        final ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes).normalize();
        final CipherUtil cipher = new CipherUtil();
        cipher.Init_dec(userD, c1);
        cipher.Decrypt(c2);
        cipher.Dofinal(c3);
        return (new String(c3)).equals(new String(tc3)) ? c2 : null;
    }

    private static byte[] sign(byte[] userId, byte[] privateKey, byte[] sourceData) throws IOException {
        if (privateKey == null || privateKey.length == 0) {
            return null;
        }

        if (sourceData == null || sourceData.length == 0) {
            return null;
        }

        byte[] userIdTmp = new byte[] { 104, 117, 115, 115, 97, 114, 86, 56, 104, 117, 115, 115, 97, 114, 86, 56 };
        if (userId == null) {
            userId = new byte[16];
            System.arraycopy(userIdTmp, 0, userId, 0, 16);
        }
        if (userId.length < 1) {
            return null;
        } else {
            BigInteger userD = new BigInteger(1, privateKey);
            ECPoint userKey = sm2.ecc_point_g.multiply(userD).normalize();
//            if (userKey.getX() == null) {
            if (userKey.getAffineXCoord() == null) {
                return null;
            } else {
                SM3Util sm3 = new SM3Util();
                byte[] z = sm2.sm2GetZ(userId, userKey);
                sm3.update(z, 0, z.length);
                sm3.update(sourceData, 0, sourceData.length);
                byte[] md = new byte[32];
                sm3.doFinal(md, 0);

                SM2Result sm2Result = new SM2Result();
                sm2.sm2Sign(md, userD, userKey, sm2Result);

                String sr = sm2Result.r.toString(16);
                String ss = sm2Result.s.toString(16);
                if (sr.length() != 64) {
                    while (sr.length() != 64) {
                        sr = '0' + sr;
                    }
                }

                if (ss.length() != 64) {
                    while (ss.length() != 64) {
                        ss = '0' + ss;
                    }
                }
                return CipherUtil.hexToByte(sr + ss);
            }
        }
    }

    static boolean verifySign(byte[] userId, byte[] publicKey, byte[] sourceData, byte[] signData)
            throws IOException {
        if (publicKey == null || publicKey.length != 65) {
            return false;
        }

        if (sourceData == null || sourceData.length == 0) {
            return false;
        }
        if (signData == null || signData.length != 64) {
            return false;
        }
        byte[] userIdTmp = new byte[] { 104, 117, 115, 115, 97, 114, 86, 56, 104, 117, 115, 115, 97, 114, 86, 56 };
        if (userId == null) {
            userId = new byte[16];
            System.arraycopy(userIdTmp, 0, userId, 0, 16);
        }
        if (userId.length < 1) {
            return false;
        }

//        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);
        //升级后修改显式归一化
        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey).normalize();
        SM3Util sm3 = new SM3Util();
        byte[] z = sm2.sm2GetZ(userId, userKey);
        sm3.update(z, 0, z.length);
        sm3.update(sourceData, 0, sourceData.length);
        byte[] md = new byte[32];
        sm3.doFinal(md, 0);
        byte[] r = new byte[32];
        System.arraycopy(signData, 0, r, 0, 32);
        byte[] s = new byte[32];
        System.arraycopy(signData, 32, s, 0, 32);
        byte[] tr = new byte[32];
        byte[] ts = new byte[32];
        if (!Arrays.equals(r, tr) && !Arrays.equals(s, ts)) {
            BigInteger br = new BigInteger(1, r);
            BigInteger bs = new BigInteger(1, s);
            SM2Result sm2Result = new SM2Result();
            sm2Result.r = br;
            sm2Result.s = bs;
            sm2.sm2Verify(md, userKey, sm2Result.r, sm2Result.s, sm2Result);
            return sm2Result.r.equals(sm2Result.R);
        } else {
            return false;
        }
    }

    /**
     * SM2 加密算法
     * encrypt
     * @author: WangBinBin
     * @param plainText 明文
     * @param pubKey 公钥
     * @return 密文
     */
    public static String encrypt(String plainText, String pubKey) {
        try {
            return encrypt(CipherUtil.hexToByte(pubKey), plainText.getBytes()).toLowerCase();
        } catch (IllegalArgumentException | IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * SM2 私钥解密
     * decrypt
     * @author: WangBinBin
     * @param cipherText 密文
     * @param priKey 私钥
     * @return 明文
     */
    public static String decrypt(String cipherText, String priKey) {
        try {
            return new String(decrypt(CipherUtil.hexToByte(priKey), CipherUtil.hexToByte(cipherText)));
        } catch (IllegalArgumentException | IOException e) {
            System.err.print("SM2解密失败!" + System.getProperty("line.separator") + e.getMessage());
            return null;
        }
    }

    /**
     * 数据签名
     * sign
     * @author: WangBinBin
     * @param userId 用户ID
     * @param privateKey 私钥
     * @param sourceData 数据
     * @return 数据签名
     */
    public static String sign(String userId, String privateKey, String sourceData) {
        try {
            return Base64.encodeBase64String(
                    sign(userId.getBytes(), CipherUtil.hexToByte(privateKey), sourceData.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 验证签名
     * verifySign
     * @author: WangBinBin
     * @param userId 用户ID
     * @param publicKey 公钥
     * @param sourceData 原数据
     * @param signData 签名数据
     * @return  验证结果 true 、false
     */
    public static boolean verifySign(String userId, String publicKey, String sourceData, String signData) {
        try {
            return verifySign(userId.getBytes(), CipherUtil.hexToByte(publicKey), sourceData.getBytes(),
                    Base64.decodeBase64(signData.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

}
