對於如何從SM2的pfx證書文件中解析公鑰和私鑰,並從二次加密的密文中解密


首先呢,由於我的域名之前處理點問題,然后備案第二個網站時候,第一個網站沒法訪問,所以備案沒過,阿里雲告訴我要刪除一個網站的備案,但是他沒告訴我要刪除主體,所以我的備案主體成了空殼主體,要傳真或者發快遞到鄭州市金水區民航路8號河南省通信管理局一樓網站備案中心,或者等它自動注銷。好氣,所以我轉騰訊雲了,博客就重新寫吧。

這個事情是有一次在hutools的issues去有人提出了一個sm2加密問題,那個問題的原因是對於某些需要接入硬件加密機和軟加密的公鑰可能不太一樣,大概就是公鑰der編碼問題,然后某天微信有個人加我問我是不是cherryrum(GitHub),由於剛好過年沒回家,比較無聊,后來才知道是個妹子(后來知道的),男人不能說不行,她給我了一個測試的pfx證書還有兩段加密后的密文,然后沒了,說沒法解密,總是報錯。但是證書可以用hutools封裝的方法進行加解密。

  1. pfx證書:
    代碼下載
    代碼和pfx證書文件
    證書密碼:111111

下面是證書Code
MIIIJgIBAzCCB+AGCSqGSIb3DQEHAaCCB9EEggfNMIIHyTCCArYGCSqGSIb3DQEHAaCCAqcEggKjMIICnzCCApsGCyqGSIb3DQEMCgECoIICQjCCAj4wKAYKKoZIhvcNAQwBAzAaBBREDAPbdO94uB3FzdlZ2dRDuZliuAICBAAEggIQ92ZQTuoVmTBT/7ylAr1yah+u8CxWvHccIgW3lbrHVR7zvUkucci1/sIhMhhUtMNR+HRT/E7W4bGP+tT/rGVX1KUIc3Gzb+Om5vo/3IxHDLAsIyJ2KIiCd7r8JItXXpexXQBI56byxQEZx1Za7THwYFmhybS25IP2p0H+n5h51C1Ii2e6S2NZTCLgPfw2Xip+0TejcoDMj32Av29Bak4qsnLcpD7wu/6Oge04TPoChiHATLnF6lwkkQ64lqMFVhRh/t0QVY9WKaZZD4vyBZTOxz6LQST4BNLBgLFsTW0+cFlAhVSM4LThTmTGxUAgMS/3SR/mBvQATfLOAy9xd9nQrBCfR7aB1uV0q2+B1UusZhGygDJkRUkeAN3dstn3p4wiSkq/G6bJzg441zSl7MU7RmZZtFS5bHmWsk8r3IZ88QaWYecEX1rf4rqh7OKjKSFlOVTLeT4v6CJcaNcq1Z93nAJaAgDdjmepqvnJz4FlPnw8LddBLPQJ/yY0qLe8pspaexuNCRyn9dxCnNMwG8kVkTqlujlNVkwx5TUQ/FCMYCWedyYpPmMdweOzsiu7rKcJDb/pqE9vPHewBz74+yHntPefB3M8CDUen6HgeH8XabTwPkUp69iEpZ4l7Czg6547+L4DsWr07RONVhR2VercqjzIR0MI1a9IN8Tnu+Y8D7A8Qjo07YcWnGp6SYEy0G/TMUYwHwYJKoZIhvcNAQkUMRIeEAB1AHMAZQByACAAawBlAHkwIwYJKoZIhvcNAQkVMRYEFOhTryibpsBDZhWrYZC75hiCaEFNMIIFCwYJKoZIhvcNAQcGoIIE/DCCBPgCAQAwggTxBgkqhkiG9w0BBwEwKAYKKoZIhvcNAQwBBjAaBBQF6r64t6q79ugJUThgxqhlKY7rGQICBACAggS40QjpkFRTMaqjPX/T7K1+x4LAuToay8FijhJoapeAyp9FYR65eEPNeJWVLuKsmlvE87WuB/UG2UbyPW0l/cEwvqPj7A8jOGAhMNhFSgUtx+7MjBbFCja4pcO4Vz35qtDJrCwNN7ZdhWrrG1v5PyBs4VnpHyJCBlpHmy5bDr6Tdkxj9jSXWxxioiMV6Ay0M7N6gPgxtbApFfKTmTxiBOexrsJAdBaILL/39fDUG/5kjGuq5t5qkO4w+/gpjGp8aeecGtRCle2j59DkMSpUHyK8TnYo2aA+m972Ef06DqSyv1+ni6dPtWA/+6Z9RfvumMJcmZHjqrVaf9VoWGcjO37JZ0w8HPX9qKu9neBXCW0qe7Xd25MtTR7Zvbg9cumKpqFfjF+ni3ML8uFzG61AQx5l3bKxtFDY5Y99G8wfAE4gpFU5gybs3v+eJf+hCPBjaMWz9kEZ9cbFFXgRnf1Wo8uWt5kfF2ClR+4RvGx+TKopd3psDFLX4JpbCdcq5/7r4qKmM5Hk92blOBD3kOFcJf0BJaFj6USrosUdgkALBywKENp5Fyjusx5h8fhleBJ56ZFxbdLkdzhkLFeAHu6XHeJRMCj1pF++1yF8AAzzZaC7exgu5u/Jfs5xPRwFrCyrvopQCZdW2v9YpSp6bUue08P0GBB3MN8L36MakhMoYSVcTzRtX63DSlfBjjUGcU7Tq1X4xNfHc40kxny1y8oeIij/P9WEB95V38TIvJhQyshYTsEyhuKcKhSIMhVy3ayzQNOq2b6yuXUSd75kc6fe2vSEk935ShPmcMWQhMJmguTU4D8SEDej6399m3KuvEbjXZuS2qyS4F9O8IhL3tynTGWJOrMXKfr4F5BpEVoCUABHOtVbgoWoTOxHCCUkPCKELcJ0tBJvUk3jXnxCezXmeYhDpuhIEgE4Z9VJLHaysxdRQIQebMzM2oHgGz88r7SE9n90LHWV4rc5jhCA5neiOBAa/pViJo03AcddvRajjDS0KIgaIDUI8Dhz/Oofj5sqThgggGFw6UCoucf8jGu28m++pvP5z3zNaloTO/1rQyD9cWdRC8oxrG9y1NB5FAi+F0/ULV9g1IwbJfGM3c7wiXdY36IEikjZXpLE76J06Po/cPxMyZoKMLCEkISJ2vzonA5y56KyIV66AclxWzboGqzK/I2PLG+9Bk9+1J5BiwL5Gm8X5tmck7JYrz469iQfyWN25qv9z4uf4SxuJ199DGq0GroMxO4Q7O+CK8YdURZZuOCZ5wGjRNj0cfNEDQu/N0yHBSfY+iXPD1HxOkQgVZRstubld9+PCQAYgNvr6o4lbIUdCMKeMqVBEx01F5ARlGpkhyZZAUO/GW0QmkcrY4JPJv9q2QoYI6T4Wx6EGKHls1CGZ+cNDxEI19qurW1zcS86K0lY2E+84TcutkpfHiiuO68T7dRg/eO3hXq07LVql3RXiX89BRQmy2IJIyFzpv46wS3HVJeK517aYnBxKM63leyzV6YDSI4L2mePF3zLnsmb8fwHeC2Lk+x3dtLMHYQi9fKkGINKfLKrXA+Qht/ijhvU1GX+WDG97OShiFU0OhMCD/mfItWZG7J1WxNEmb+Is2t4AAf1wf8wPTAhMAkGBSsOAwIaBQAEFJklMUR1BaO3CmVltCh07+rKpXfrBBR3hTx3xARflWgNV7WZ5ks5Pjdg0wICBAA=

  1. 密文
  • PEMxPkJIamRTZUxmZ2MvSzhPZDZDVjQrS3plZlA5L2Qzb2p6RzVveW9qVmFCU0pMb29lZEd4UUM1bEFvcEh6aVM5TFlldDJwNWkzck1uK25qWU9YYVpPZEtaOD08L0MxPjxDMj5HYzBXTCtzNWpSNjBRdjBFWnVQdHZ2cXpCZFJEK25ZbnhTTHJhcXhjTU9wRHB6cVlNSVhNdDV3L2piQ2xJakJISWpFUWQ5L0s1QjliMHR5eE9qTlo5Tkh5TTZRQU45TEppR3FlejZGSE5Jb1kxTlBvSDRjaEd0NWw8L0MyPjxDMz5iTGd1RWY5S01CTkNDZzExOUgvK2x4NFBsZTc2UUNqREVVOC9sSkhXcTJ3PTwvQzM+

  • PEMxPkJCK2JyencxbUFwcjBoVjQ0N25MM2VObUxuV2NENEZzd1IzU2VRYUt3dC9tM0ExaTlhYnZSYjZaeXMwVHRKc0xYZ0RsSnhkdmVlSGJNbXZyZnFFcVd0RT08L0MxPjxDMj52djNWVmtab2cxWHpYMkdhcEtISFliQmpLbmRQUzdvNEhkYk84RzZhWHZ5OU9wU25TeTBoRzhSWk04T2NVdVIwWDRVSVVtZjArY1N4alFTeEwybkJLTE5jcTd2bFRDdjRxdFpCZS96RnZpblB0N1Ziek9WdVlLK0VNS2lxbkE9PTwvQzI+PEMzPitXWDluSWpscUw3b1NiamQzREpXTjhHN1o0TFRkWE1mODVQWTdrNlVQdDA9PC9DMz4=

下面是解密流程 開始我看見密文發現前面的幾個字符是一樣的,證明很大可能密文經過相同的加密方法,這玩意一看就是Base64編碼的。
解碼結果

  • <C1>BHjdSeLfgc/K8Od6CV4+KzefP9/d3ojzG5oyojVaBSJLooedGxQC5lAopHziS9LYet2p5i3rMn+njYOXaZOdKZ8=</C1><C2>Gc0WL+s5jR60Qv0EZuPtvvqzBdRD+nYnxSLraqxcMOpDpzqYMIXMt5w/jbClIjBHIjEQd9/K5B9b0tyxOjNZ9NHyM6QAN9LJiGqez6FHNIoY1NPoH4chGt5l</C2><C3>bLguEf9KMBNCCg119H/+lx4Ple76QCjDEU8/lJHWq2w=</C3>

  • <C1>BB+brzw1mApr0hV447nL3eNmLnWcD4FswR3SeQaKwt/m3A1i9abvRb6Zys0TtJsLXgDlJxdveeHbMmvrfqEqWtE=</C1><C2>vv3VVkZog1XzX2GapKHHYbBjKndPS7o4HdbO8G6aXvy9OpSnSy0hG8RZM8OcUuR0X4UIUmf0+cSxjQSxL2nBKLNcq7vlTCv4qtZBe/zFvinPt7VbzOVuYK+EMKiqnA==</C2><C3>+WX9nIjlqL7oSbjd3DJWN8G7Z4LTdXMf85PY7k6UPt0=</C3>

對於ctfer最好就是把解密字符打印出來,更直觀的看到是什么,看到上面的<C1></C1>、<C2></C2>、<C3></C3>,了解SM2的大部分都會想到加密后的密文排列方式指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2,知道小姐姐對接的系統比較老,那么以這些老企業的尿性肯定不可能使用新的標准,(我才不會說我TMD自作聰明使用新的標准沒解密出來。

那么提取上面的標簽里面的value 代碼自己寫(正則或者當作HTML標簽解析),當作字符串String如下所示,准備工作做完,下面是Show code time(我是cv神教的)

  • "BHjdSeLfgc/K8Od6CV4+KzefP9/d3ojzG5oyojVaBSJLooedGxQC5lAopHziS9LYet2p5i3rMn+njYOXaZOdKZ8=Gc0WL+s5jR60Qv0EZuPtvvqzBdRD+nYnxSLraqxcMOpDpzqYMIXMt5w/jbClIjBHIjEQd9/K5B9b0tyxOjNZ9NHyM6QAN9LJiGqez6FHNIoY1NPoH4chGt5lbLguEf9KMBNCCg119H/+lx4Ple76QCjDEU8/lJHWq2w=\n"

  • "BB+brzw1mApr0hV447nL3eNmLnWcD4FswR3SeQaKwt/m3A1i9abvRb6Zys0TtJsLXgDlJxdveeHbMmvrfqEqWtE=vv3VVkZog1XzX2GapKHHYbBjKndPS7o4HdbO8G6aXvy9OpSnSy0hG8RZM8OcUuR0X4UIUmf0+cSxjQSxL2nBKLNcq7vlTCv4qtZBe/zFvinPt7VbzOVuYK+EMKiqnA==+WX9nIjlqL7oSbjd3DJWN8G7Z4LTdXMf85PY7k6UPt0=\n"


  1. 讀取pfx文件
import java.io.IOException;
import java.io.RandomAccessFile;
//這個地方可以使用try-with-resources
public class FileUtil {

  public static void writeFile(String filePath, byte[] data) throws IOException {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(filePath, "rw");
            raf.write(data);
        } finally {
            if (raf != null) {
                raf.close();
            }
        }
    }
    public static byte[] readFile(String filePath) throws IOException {
        RandomAccessFile raf = null;
        byte[] data;
        try {
            raf = new RandomAccessFile(filePath, "r");
            data = new byte[(int) raf.length()];
            raf.read(data);
            return data;
        } finally {
            if (raf != null) {
                raf.close();
            }
        }
    }
}

2.Base64轉換工具


/**
 * @program:
 * @description:
 * @author: lin
 * @create: 2021-02-21 04:21
 */

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.*;

/**
 * Base64 轉換工具
 */
public class Base64Utils {

    /**
     * byte數組 轉換為 Base64字符串
     */
    public static String encode(byte[] data) {
        return new BASE64Encoder().encode(data);
    }

    /**
     * Base64字符串 轉換為 byte數組
     */
    public static byte[] decode(String base64) {
        try {
            return new BASE64Decoder().decodeBuffer(base64);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new byte[0];
    }

    /**
     * 把文件內容編碼為 Base64字符串, 只能編碼小文件(例如文本、圖片等)
     */
    public static String encodeFile(File file) throws Exception {
        InputStream in = null;
        ByteArrayOutputStream bytesOut = null;

        try {
            in = new FileInputStream(file);
            bytesOut = new ByteArrayOutputStream((int) file.length());

            byte[] buf = new byte[1024];
            int len = -1;

            while ((len = in.read(buf)) != -1) {
                bytesOut.write(buf, 0, len);
            }
            bytesOut.flush();

            return encode(bytesOut.toByteArray());

        } finally {
            close(in);
            close(bytesOut);
        }
    }

    /**
     * 把 Base64字符串 轉換為 byte數組, 保存到指定文件
     */
    public static void decodeFile(String base64, File file) throws Exception {
        OutputStream fileOut = null;
        try {
            fileOut = new FileOutputStream(file);
            fileOut.write(decode(base64));
            fileOut.flush();
        } finally {
            close(fileOut);
        }
    }

    private static void close(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                // nothing
            }
        }
    }

}

3.SM2工具類
參考gayhubhttps://github.com/ZZMarquis/gmhelper
當然也可以使用hutools或者直接使用bouncycastle

BCECUtil.java

import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X962Parameters;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9ECPoint;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;

import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * 這個工具類的方法,也適用於其他基於BC庫的ECC算法
 */
public class BCECUtil {
    private static final String ALGO_NAME_EC = "EC";
    private static final String PEM_STRING_PUBLIC = "PUBLIC KEY";
    private static final String PEM_STRING_ECPRIVATEKEY = "EC PRIVATE KEY";

    /**
     * 生成ECC密鑰對
     *
     * @return ECC密鑰對
     */
    public static AsymmetricCipherKeyPair generateKeyPairParameter(
            ECDomainParameters domainParameters, SecureRandom random) {
        ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters,
                random);
        ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
        keyGen.init(keyGenerationParams);
        return keyGen.generateKeyPair();
    }

    public static KeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random)
            throws NoSuchProviderException, NoSuchAlgorithmException,
            InvalidAlgorithmParameterException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        ECParameterSpec parameterSpec = new ECParameterSpec(domainParameters.getCurve(), domainParameters.getG(),
                domainParameters.getN(), domainParameters.getH());
        kpg.initialize(parameterSpec, random);
        return kpg.generateKeyPair();
    }

    public static int getCurveLength(ECKeyParameters ecKey) {
        return getCurveLength(ecKey.getParameters());
    }

    public static int getCurveLength(ECDomainParameters domainParams) {
        return (domainParams.getCurve().getFieldSize() + 7) / 8;
    }

    public static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) {
        if (src.length == curveLength) {
            return src;
        }

        byte[] result = new byte[curveLength];
        if (src.length > curveLength) {
            System.arraycopy(src, src.length - result.length, result, 0, result.length);
        } else {
            System.arraycopy(src, 0, result, result.length - src.length, src.length);
        }
        return result;
    }

    /**
     * @param dHex             十六進制字符串形式的私鑰d值,如果是SM2算法,Hex字符串長度應該是64(即32字節)
     * @param domainParameters EC Domain參數,一般是固定的,如果是SM2算法的可參考{@link SM2Util#DOMAIN_PARAMS}
     * @return
     */
    public static ECPrivateKeyParameters createECPrivateKeyParameters(
            String dHex, ECDomainParameters domainParameters) {
        return createECPrivateKeyParameters(ByteUtils.fromHexString(dHex), domainParameters);
    }

    /**
     * @param dBytes           字節數組形式的私鑰d值,如果是SM2算法,應該是32字節
     * @param domainParameters EC Domain參數,一般是固定的,如果是SM2算法的可參考{@link SM2Util#DOMAIN_PARAMS}
     * @return
     */
    public static ECPrivateKeyParameters createECPrivateKeyParameters(
            byte[] dBytes, ECDomainParameters domainParameters) {
        return createECPrivateKeyParameters(new BigInteger(1, dBytes), domainParameters);
    }

    /**
     * @param d                大數形式的私鑰d值
     * @param domainParameters EC Domain參數,一般是固定的,如果是SM2算法的可參考{@link SM2Util#DOMAIN_PARAMS}
     * @return
     */
    public static ECPrivateKeyParameters createECPrivateKeyParameters(
            BigInteger d, ECDomainParameters domainParameters) {
        return new ECPrivateKeyParameters(d, domainParameters);
    }

    /**
     * 根據EC私鑰構造EC公鑰
     *
     * @param priKey ECC私鑰參數對象
     * @return
     */
    public static ECPublicKeyParameters buildECPublicKeyByPrivateKey(ECPrivateKeyParameters priKey) {
        ECDomainParameters domainParameters = priKey.getParameters();
        ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), priKey.getD());
        return new ECPublicKeyParameters(q, domainParameters);
    }

    /**
     * @param x                大數形式的公鑰x分量
     * @param y                大數形式的公鑰y分量
     * @param curve            EC曲線參數,一般是固定的,如果是SM2算法的可參考{@link SM2Util#CURVE}
     * @param domainParameters EC Domain參數,一般是固定的,如果是SM2算法的可參考{@link SM2Util#DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            BigInteger x, BigInteger y, ECCurve curve, ECDomainParameters domainParameters) {
        return createECPublicKeyParameters(x.toByteArray(), y.toByteArray(), curve, domainParameters);
    }

    /**
     * @param xHex             十六進制形式的公鑰x分量,如果是SM2算法,Hex字符串長度應該是64(即32字節)
     * @param yHex             十六進制形式的公鑰y分量,如果是SM2算法,Hex字符串長度應該是64(即32字節)
     * @param curve            EC曲線參數,一般是固定的,如果是SM2算法的可參考{@link SM2Util#CURVE}
     * @param domainParameters EC Domain參數,一般是固定的,如果是SM2算法的可參考{@link SM2Util#DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
        return createECPublicKeyParameters(ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex),
                curve, domainParameters);
    }

    /**
     * @param xBytes           十六進制形式的公鑰x分量,如果是SM2算法,應該是32字節
     * @param yBytes           十六進制形式的公鑰y分量,如果是SM2算法,應該是32字節
     * @param curve            EC曲線參數,一般是固定的,如果是SM2算法的可參考{@link SM2Util#CURVE}
     * @param domainParameters EC Domain參數,一般是固定的,如果是SM2算法的可參考{@link SM2Util#DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) {
        final byte uncompressedFlag = 0x04;
        int curveLength = getCurveLength(domainParameters);
        xBytes = fixToCurveLengthBytes(curveLength, xBytes);
        yBytes = fixToCurveLengthBytes(curveLength, yBytes);
        byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
        encodedPubKey[0] = uncompressedFlag;
        System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
        System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
        return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
    }

    public static ECPrivateKeyParameters convertPrivateKeyToParameters(BCECPrivateKey ecPriKey) {
        ECParameterSpec parameterSpec = ecPriKey.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
                parameterSpec.getN(), parameterSpec.getH());
        return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters);
    }

    public static ECPublicKeyParameters convertPublicKeyToParameters(BCECPublicKey ecPubKey) {
        ECParameterSpec parameterSpec = ecPubKey.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
                parameterSpec.getN(), parameterSpec.getH());
        return new ECPublicKeyParameters(ecPubKey.getQ(), domainParameters);
    }

    public static BCECPublicKey createPublicKeyFromSubjectPublicKeyInfo(SubjectPublicKeyInfo subPubInfo)
            throws NoSuchProviderException,
            NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        return BCECUtil.convertX509ToECPublicKey(subPubInfo.toASN1Primitive().getEncoded(ASN1Encoding.DER));
    }

    /**
     * 將ECC私鑰轉換為PKCS8標准的字節流
     *
     * @param priKey
     * @param pubKey 可以為空,但是如果為空的話得到的結果OpenSSL可能解析不了
     * @return
     */
    public static byte[] convertECPrivateKeyToPKCS8(
            ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
        ECDomainParameters domainParams = priKey.getParameters();
        ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
                domainParams.getN(), domainParams.getH());
        BCECPublicKey publicKey = null;
        if (pubKey != null) {
            publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
                    BouncyCastleProvider.CONFIGURATION);
        }
        BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey,
                spec, BouncyCastleProvider.CONFIGURATION);
        return privateKey.getEncoded();
    }

    /**
     * 將PKCS8標准的私鑰字節流轉換為私鑰對象
     *
     * @param pkcs8Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     */
    public static BCECPrivateKey convertPKCS8ToECPrivateKey(byte[] pkcs8Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
        PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(pkcs8Key);
        KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPrivateKey) kf.generatePrivate(peks);
    }

    /**
     * 將PKCS8標准的私鑰字節流轉換為PEM
     *
     * @param encodedKey
     * @return
     * @throws IOException
     */
    public static String convertECPrivateKeyPKCS8ToPEM(byte[] encodedKey) throws IOException {
        return convertEncodedDataToPEM(PEM_STRING_ECPRIVATEKEY, encodedKey);
    }

    /**
     * 將PEM格式的私鑰轉換為PKCS8標准字節流
     *
     * @param pemString
     * @return
     * @throws IOException
     */
    public static byte[] convertECPrivateKeyPEMToPKCS8(String pemString) throws IOException {
        return convertPEMToEncodedData(pemString);
    }

    /**
     * 將ECC私鑰轉換為SEC1標准的字節流
     * openssl d2i_ECPrivateKey函數要求的DER編碼的私鑰也是SEC1標准的,
     * 這個工具函數的主要目的就是為了能生成一個openssl可以直接“識別”的ECC私鑰.
     * 相對RSA私鑰的PKCS1標准,ECC私鑰的標准為SEC1
     *
     * @param priKey
     * @param pubKey
     * @return
     * @throws IOException
     */
    public static byte[] convertECPrivateKeyToSEC1(
            ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) throws IOException {
        byte[] pkcs8Bytes = convertECPrivateKeyToPKCS8(priKey, pubKey);
        PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes);
        ASN1Encodable encodable = pki.parsePrivateKey();
        ASN1Primitive primitive = encodable.toASN1Primitive();
        byte[] sec1Bytes = primitive.getEncoded();
        return sec1Bytes;
    }

    /**
     * 將SEC1標准的私鑰字節流恢復為PKCS8標准的字節流
     *
     * @param sec1Key
     * @return
     * @throws IOException
     */
    public static byte[] convertECPrivateKeySEC1ToPKCS8(byte[] sec1Key) throws IOException {
        /**
         * 參考org.bouncycastle.asn1.pkcs.PrivateKeyInfo和
         * org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey,逆向拼裝
         */
        X962Parameters params = getDomainParametersFromName(SM2Util.JDK_EC_SPEC, false);
        ASN1OctetString privKey = new DEROctetString(sec1Key);
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(0)); //版本號
        v.add(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params)); //算法標識
        v.add(privKey);
        DERSequence ds = new DERSequence(v);
        return ds.getEncoded(ASN1Encoding.DER);
    }

    /**
     * 將SEC1標准的私鑰字節流轉為BCECPrivateKey對象
     *
     * @param sec1Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws IOException
     */
    public static BCECPrivateKey convertSEC1ToBCECPrivateKey(byte[] sec1Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(convertECPrivateKeySEC1ToPKCS8(sec1Key));
        KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPrivateKey) kf.generatePrivate(peks);
    }

    /**
     * 將SEC1標准的私鑰字節流轉為ECPrivateKeyParameters對象
     * openssl i2d_ECPrivateKey函數生成的DER編碼的ecc私鑰是:SEC1標准的、帶有EC_GROUP、帶有公鑰的,
     * 這個工具函數的主要目的就是為了使Java程序能夠“識別”openssl生成的ECC私鑰
     *
     * @param sec1Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     */
    public static ECPrivateKeyParameters convertSEC1ToECPrivateKey(byte[] sec1Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        BCECPrivateKey privateKey = convertSEC1ToBCECPrivateKey(sec1Key);
        return convertPrivateKeyToParameters(privateKey);
    }

    /**
     * 將ECC公鑰對象轉換為X509標准的字節流
     *
     * @param pubKey
     * @return
     */
    public static byte[] convertECPublicKeyToX509(ECPublicKeyParameters pubKey) {
        ECDomainParameters domainParams = pubKey.getParameters();
        ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
                domainParams.getN(), domainParams.getH());
        BCECPublicKey publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
                BouncyCastleProvider.CONFIGURATION);
        return publicKey.getEncoded();
    }

    /**
     * 將X509標准的公鑰字節流轉為公鑰對象
     *
     * @param x509Bytes
     * @return
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static BCECPublicKey convertX509ToECPublicKey(byte[] x509Bytes) throws NoSuchProviderException,
            NoSuchAlgorithmException, InvalidKeySpecException {
        X509EncodedKeySpec eks = new X509EncodedKeySpec(x509Bytes);
        KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPublicKey) kf.generatePublic(eks);
    }

    /**
     * 將X509標准的公鑰字節流轉為PEM
     *
     * @param encodedKey
     * @return
     * @throws IOException
     */
    public static String convertECPublicKeyX509ToPEM(byte[] encodedKey) throws IOException {
        return convertEncodedDataToPEM(PEM_STRING_PUBLIC, encodedKey);
    }

    /**
     * 將PEM格式的公鑰轉為X509標准的字節流
     *
     * @param pemString
     * @return
     * @throws IOException
     */
    public static byte[] convertECPublicKeyPEMToX509(String pemString) throws IOException {
        return convertPEMToEncodedData(pemString);
    }

    /**
     * copy from BC
     *
     * @param genSpec
     * @return
     */
    public static X9ECParameters getDomainParametersFromGenSpec(ECGenParameterSpec genSpec) {
        return getDomainParametersFromName(genSpec.getName());
    }

    /**
     * copy from BC
     *
     * @param curveName
     * @return
     */
    public static X9ECParameters getDomainParametersFromName(String curveName) {
        X9ECParameters domainParameters;
        try {
            if (curveName.charAt(0) >= '0' && curveName.charAt(0) <= '2') {
                ASN1ObjectIdentifier oidID = new ASN1ObjectIdentifier(curveName);
                domainParameters = ECUtil.getNamedCurveByOid(oidID);
            } else {
                if (curveName.indexOf(' ') > 0) {
                    curveName = curveName.substring(curveName.indexOf(' ') + 1);
                    domainParameters = ECUtil.getNamedCurveByName(curveName);
                } else {
                    domainParameters = ECUtil.getNamedCurveByName(curveName);
                }
            }
        } catch (IllegalArgumentException ex) {
            domainParameters = ECUtil.getNamedCurveByName(curveName);
        }
        return domainParameters;
    }

    /**
     * copy from BC
     *
     * @param ecSpec
     * @param withCompression
     * @return
     */
    public static X962Parameters getDomainParametersFromName(
            java.security.spec.ECParameterSpec ecSpec, boolean withCompression) {
        X962Parameters params;

        if (ecSpec instanceof ECNamedCurveSpec) {
            ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec) ecSpec).getName());
            if (curveOid == null) {
                curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec) ecSpec).getName());
            }
            params = new X962Parameters(curveOid);
        } else if (ecSpec == null) {
            params = new X962Parameters(DERNull.INSTANCE);
        } else {
            ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());

            X9ECParameters ecP = new X9ECParameters(
                    curve,
                    new X9ECPoint(EC5Util.convertPoint(curve, ecSpec.getGenerator()), withCompression),
                    ecSpec.getOrder(),
                    BigInteger.valueOf(ecSpec.getCofactor()),
                    ecSpec.getCurve().getSeed());

            //// 如果是1.62或更低版本的bcprov-jdk15on應該使用以下這段代碼,因為高版本的EC5Util.convertPoint沒有向下兼容
            /*
            X9ECParameters ecP = new X9ECParameters(
                curve,
                EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
                ecSpec.getOrder(),
                BigInteger.valueOf(ecSpec.getCofactor()),
                ecSpec.getCurve().getSeed());
            */

            params = new X962Parameters(ecP);
        }

        return params;
    }

    private static String convertEncodedDataToPEM(String type, byte[] encodedData) throws IOException {
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
        try {
            PemObject pemObj = new PemObject(type, encodedData);
            pWrt.writeObject(pemObj);
        } finally {
            pWrt.close();
        }
        return new String(bOut.toByteArray());
    }

    private static byte[] convertPEMToEncodedData(String pemString) throws IOException {
        ByteArrayInputStream bIn = new ByteArrayInputStream(pemString.getBytes());
        PemReader pRdr = new PemReader(new InputStreamReader(bIn));
        try {
            PemObject pemObject = pRdr.readPemObject();
            return pemObject.getContent();
        } finally {
            pRdr.close();
        }
    }
}

SM2CertUtil.java



import org.bouncycastle.asn1.pkcs.ContentInfo;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS12PfxPdu;
import org.bouncycastle.pkcs.PKCS12SafeBag;
import org.bouncycastle.pkcs.PKCS12SafeBagFactory;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;

import java.io.*;
import java.security.NoSuchProviderException;
import java.security.cert.*;
import java.util.List;

public class SM2CertUtil {
    public SM2CertUtil() throws Exception {
    }

    public static BCECPublicKey getBCECPublicKey(X509Certificate sm2Cert) {
        ECPublicKey pubKey = (ECPublicKey) sm2Cert.getPublicKey();
        ECPoint q = pubKey.getQ();
        ECParameterSpec parameterSpec = new ECParameterSpec(SM2Util.CURVE, SM2Util.G_POINT,
            SM2Util.SM2_ECC_N, SM2Util.SM2_ECC_H);
        ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(q, parameterSpec);
        return new BCECPublicKey(pubKey.getAlgorithm(), pubKeySpec,
            BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * 校驗證書
     *
     * @param issuerPubKey 從頒發者CA證書中提取出來的公鑰
     * @param cert         待校驗的證書
     * @return
     */
    public static boolean verifyCertificate(BCECPublicKey issuerPubKey, X509Certificate cert) {
        try {
            cert.verify(issuerPubKey, BouncyCastleProvider.PROVIDER_NAME);
        } catch (Exception ex) {
            return false;
        }
        return true;
    }

    public static X509Certificate getX509Certificate(String certFilePath) throws IOException, CertificateException,
        NoSuchProviderException {
        InputStream is = null;
        try {
            is = new FileInputStream(certFilePath);
            return getX509Certificate(is);
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    public static X509Certificate getX509Certificate(byte[] certBytes) throws CertificateException,
        NoSuchProviderException {
        ByteArrayInputStream bais = new ByteArrayInputStream(certBytes);
        return getX509Certificate(bais);
    }

    public static X509Certificate getX509Certificate(InputStream is) throws CertificateException,
        NoSuchProviderException {
        CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        return (X509Certificate) cf.generateCertificate(is);
    }

    public static CertPath getCertificateChain(String certChainPath) throws IOException, CertificateException,
        NoSuchProviderException {
        InputStream is = null;
        try {
            is = new FileInputStream(certChainPath);
            return getCertificateChain(is);
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    public static CertPath getCertificateChain(byte[] certChainBytes) throws CertificateException,
        NoSuchProviderException {
        ByteArrayInputStream bais = new ByteArrayInputStream(certChainBytes);
        return getCertificateChain(bais);
    }

    public static byte[] getCertificateChainBytes(CertPath certChain) throws CertificateEncodingException {
        return certChain.getEncoded("PKCS7");
    }

    public static CertPath getCertificateChain(InputStream is) throws CertificateException, NoSuchProviderException {
        CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        return cf.generateCertPath(is, "PKCS7");
    }

    public static CertPath getCertificateChain(List<X509Certificate> certs) throws CertificateException,
        NoSuchProviderException {
        CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        return cf.generateCertPath(certs);
    }

    public static X509Certificate getX509CertificateFromPfx(byte[] pfxDER, String passwd) throws Exception {
        InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder()
            .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passwd.toCharArray());
        PKCS12PfxPdu pfx = new PKCS12PfxPdu(pfxDER);

        ContentInfo[] infos = pfx.getContentInfos();
        if (infos.length != 2) {
            throw new Exception("Only support one pair ContentInfo");
        }

        for (int i = 0; i != infos.length; i++) {
            if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData)) {
                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider);
                PKCS12SafeBag[] bags = dataFact.getSafeBags();
                X509CertificateHolder certHoler = (X509CertificateHolder) bags[0].getBagValue();
                return SM2CertUtil.getX509Certificate(certHoler.getEncoded());
            }
        }

        throw new Exception("Not found X509Certificate in this pfx");
    }

    public static BCECPublicKey getPublicKeyFromPfx(byte[] pfxDER, String passwd) throws Exception {
        return SM2CertUtil.getBCECPublicKey(getX509CertificateFromPfx(pfxDER, passwd));
    }

    public static BCECPrivateKey getPrivateKeyFromPfx(byte[] pfxDER, String passwd) throws Exception {
        InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder()
            .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passwd.toCharArray());
        PKCS12PfxPdu pfx = new PKCS12PfxPdu(pfxDER);

        ContentInfo[] infos = pfx.getContentInfos();
        if (infos.length != 2) {
            throw new Exception("Only support one pair ContentInfo");
        }

        for (int i = 0; i != infos.length; i++) {
            if (!infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData)) {
                PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i]);
                PKCS12SafeBag[] bags = dataFact.getSafeBags();
                PKCS8EncryptedPrivateKeyInfo encInfo = (PKCS8EncryptedPrivateKeyInfo) bags[0].getBagValue();
                PrivateKeyInfo info = encInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
                BCECPrivateKey privateKey = BCECUtil.convertPKCS8ToECPrivateKey(info.getEncoded());
                return privateKey;
            }
        }

        throw new Exception("Not found Private Key in this pfx");
    }
    public static byte[] InputStream2ByteArray(String filePath) throws IOException {

        InputStream in = new FileInputStream(filePath);
        byte[] data = toByteArray(in);
        in.close();

        return data;
    }

    public  static byte[] toByteArray(InputStream in) throws IOException {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024 * 4];
        int n = 0;
        while ((n = in.read(buffer)) != -1) {
            out.write(buffer, 0, n);
        }
        return out.toByteArray();
    }
}

SM2Cipher.java

package com.study.lin.cherrypro.Utils;

public class SM2Cipher {
    /**
     * ECC密鑰
     */
    private byte[] c1;

    /**
     * 真正的密文
     */
    private byte[] c2;

    /**
     * 對(c1+c2)的SM3-HASH值
     */
    private byte[] c3;

    /**
     * SM2標准的密文,即(c1+c2+c3)
     */
    private byte[] cipherText;

    public byte[] getC1() {
        return c1;
    }

    public void setC1(byte[] c1) {
        this.c1 = c1;
    }

    public byte[] getC2() {
        return c2;
    }

    public void setC2(byte[] c2) {
        this.c2 = c2;
    }

    public byte[] getC3() {
        return c3;
    }

    public void setC3(byte[] c3) {
        this.c3 = c3;
    }

    public byte[] getCipherText() {
        return cipherText;
    }

    public void setCipherText(byte[] cipherText) {
        this.cipherText = cipherText;
    }
}

SM2Util.java


import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;


import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECFieldFp;
import java.security.spec.EllipticCurve;

public class SM2Util {
    //////////////////////////////////////////////////////////////////////////////////////
    /*
     * 以下為SM2推薦曲線參數
     */
    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    public final static BigInteger SM2_ECC_P = CURVE.getQ();
    public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
    public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
    public final static BigInteger SM2_ECC_N = CURVE.getOrder();
    public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
    public final static BigInteger SM2_ECC_GX = new BigInteger(
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    public final static BigInteger SM2_ECC_GY = new BigInteger(
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
    public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
            SM2_ECC_N, SM2_ECC_H);
    public static final int CURVE_LEN = BCECUtil.getCurveLength(DOMAIN_PARAMS);
    //////////////////////////////////////////////////////////////////////////////////////

    public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
    public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
            G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
    public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
            JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());

    //////////////////////////////////////////////////////////////////////////////////////

    public static final int SM3_DIGEST_LENGTH = 32;

    /**
     * 生成ECC密鑰對
     *
     * @return ECC密鑰對
     */
    public static AsymmetricCipherKeyPair generateKeyPairParameter() {
        SecureRandom random = new SecureRandom();
        return BCECUtil.generateKeyPairParameter(DOMAIN_PARAMS, random);
    }

    /**
     * 生成ECC密鑰對
     *
     * @return
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     */
    public static KeyPair generateKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException,
            InvalidAlgorithmParameterException {
        SecureRandom random = new SecureRandom();
        return BCECUtil.generateKeyPair(DOMAIN_PARAMS, random);
    }

    /**
     * 只獲取私鑰里的d值,32字節
     *
     * @param privateKey
     * @return
     */
    public static byte[] getRawPrivateKey(BCECPrivateKey privateKey) {
        return fixToCurveLengthBytes(privateKey.getD().toByteArray());
    }

    /**
     * 只獲取公鑰里的XY分量,64字節
     *
     * @param publicKey
     * @return 64字節數組
     */
    public static byte[] getRawPublicKey(BCECPublicKey publicKey) {
        byte[] src65 = publicKey.getQ().getEncoded(false);
        byte[] rawXY = new byte[CURVE_LEN * 2];//SM2的話這里應該是64字節
        System.arraycopy(src65, 1, rawXY, 0, rawXY.length);
        return rawXY;
    }

    /**
     * @param pubKey  公鑰
     * @param srcData 原文
     * @return 默認輸出C1C3C2順序的密文。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(BCECPublicKey pubKey, byte[] srcData) throws InvalidCipherTextException {
        ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
        return encrypt(Mode.C1C3C2, pubKeyParameters, srcData);
    }

    /**
     * @param mode    指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2
     * @param pubKey  公鑰
     * @param srcData 原文
     * @return 根據mode不同,輸出的密文C1C2C3排列順序不同。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(Mode mode, BCECPublicKey pubKey, byte[] srcData) throws InvalidCipherTextException {
        ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
        return encrypt(mode, pubKeyParameters, srcData);
    }

    /**
     * @param pubKeyParameters 公鑰
     * @param srcData          原文
     * @return 默認輸出C1C3C2順序的密文。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(ECPublicKeyParameters pubKeyParameters, byte[] srcData)
            throws InvalidCipherTextException {
        return encrypt(Mode.C1C3C2, pubKeyParameters, srcData);
    }

    /**
     * @param mode             指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2
     * @param pubKeyParameters 公鑰
     * @param srcData          原文
     * @return 根據mode不同,輸出的密文C1C2C3排列順序不同。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
            throws InvalidCipherTextException {
        SM2Engine engine = new SM2Engine(mode);
        ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
        engine.init(true, pwr);
        return engine.processBlock(srcData, 0, srcData.length);
    }

    /**
     * @param priKey    私鑰
     * @param sm2Cipher 默認輸入C1C3C2順序的密文。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return 原文。SM2解密返回了數據則一定是原文,因為SM2自帶校驗,如果密文被篡改或者密鑰對不上,都是會直接報異常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(BCECPrivateKey priKey, byte[] sm2Cipher) throws InvalidCipherTextException {
        ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
        return decrypt(Mode.C1C3C2, priKeyParameters, sm2Cipher);
    }

    /**
     * @param mode      指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2
     * @param priKey    私鑰
     * @param sm2Cipher 根據mode不同,需要輸入的密文C1C2C3排列順序不同。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return 原文。SM2解密返回了數據則一定是原文,因為SM2自帶校驗,如果密文被篡改或者密鑰對不上,都是會直接報異常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(Mode mode, BCECPrivateKey priKey, byte[] sm2Cipher) throws InvalidCipherTextException {
        ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
        return decrypt(mode, priKeyParameters, sm2Cipher);
    }

    /**
     * @param priKeyParameters 私鑰
     * @param sm2Cipher        默認輸入C1C3C2順序的密文。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return 原文。SM2解密返回了數據則一定是原文,因為SM2自帶校驗,如果密文被篡改或者密鑰對不上,都是會直接報異常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
            throws InvalidCipherTextException {
        return decrypt(Mode.C1C3C2, priKeyParameters, sm2Cipher);
    }

    /**
     * @param mode             指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2
     * @param priKeyParameters 私鑰
     * @param sm2Cipher        根據mode不同,需要輸入的密文C1C2C3排列順序不同。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return 原文。SM2解密返回了數據則一定是原文,因為SM2自帶校驗,如果密文被篡改或者密鑰對不上,都是會直接報異常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
            throws InvalidCipherTextException {
        SM2Engine engine = new SM2Engine(mode);
        engine.init(false, priKeyParameters);
        return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
    }

    /**
     * 分解SM2密文
     *
     * @param cipherText 默認輸入C1C3C2順序的密文。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return
     * @throws Exception
     */
    public static SM2Cipher parseSM2Cipher(byte[] cipherText) throws Exception {
        int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
        return parseSM2Cipher(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipherText);
    }

    /**
     * 分解SM2密文
     *
     * @param mode       指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2
     * @param cipherText 根據mode不同,需要輸入的密文C1C2C3排列順序不同。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return
     */
    public static SM2Cipher parseSM2Cipher(Mode mode, byte[] cipherText) throws Exception {
        int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
        return parseSM2Cipher(mode, curveLength, SM3_DIGEST_LENGTH, cipherText);
    }

    /**
     * @param curveLength  曲線長度,SM2的話就是256位。
     * @param digestLength 摘要長度,如果是SM2的話因為默認使用SM3摘要,SM3摘要長度為32字節。
     * @param cipherText   默認輸入C1C3C2順序的密文。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return
     * @throws Exception
     */
    public static SM2Cipher parseSM2Cipher(
            int curveLength, int digestLength, byte[] cipherText) throws Exception {
        return parseSM2Cipher(Mode.C1C3C2, curveLength, digestLength, cipherText);
    }

    /**
     * 分解SM2密文
     *
     * @param mode         指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2
     * @param curveLength  曲線長度,SM2的話就是256位。
     * @param digestLength 摘要長度,如果是SM2的話因為默認使用SM3摘要,SM3摘要長度為32字節。
     * @param cipherText   根據mode不同,需要輸入的密文C1C2C3排列順序不同。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return
     */
    public static SM2Cipher parseSM2Cipher(Mode mode, int curveLength, int digestLength,
                                           byte[] cipherText) throws Exception {
        byte[] c1 = new byte[curveLength * 2 + 1];
        byte[] c2 = new byte[cipherText.length - c1.length - digestLength];
        byte[] c3 = new byte[digestLength];

        System.arraycopy(cipherText, 0, c1, 0, c1.length);
        if (mode == Mode.C1C2C3) {
            System.arraycopy(cipherText, c1.length, c2, 0, c2.length);
            System.arraycopy(cipherText, c1.length + c2.length, c3, 0, c3.length);
        } else if (mode == Mode.C1C3C2) {
            System.arraycopy(cipherText, c1.length, c3, 0, c3.length);
            System.arraycopy(cipherText, c1.length + c3.length, c2, 0, c2.length);
        } else {
            throw new Exception("Unsupported mode:" + mode);
        }

        SM2Cipher result = new SM2Cipher();
        result.setC1(c1);
        result.setC2(c2);
        result.setC3(c3);
        result.setCipherText(cipherText);
        return result;
    }

    /**
     * DER編碼密文
     *
     * @param cipher 默認輸入C1C3C2順序的密文。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return DER編碼后的密文
     * @throws IOException
     */
    public static byte[] encodeSM2CipherToDER(byte[] cipher) throws Exception {
        int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
        return encodeSM2CipherToDER(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipher);
    }

    /**
     * DER編碼密文
     *
     * @param mode   指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2
     * @param cipher 根據mode不同,需要輸入的密文C1C2C3排列順序不同。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return 按指定mode DER編碼后的密文
     * @throws Exception
     */
    public static byte[] encodeSM2CipherToDER(Mode mode, byte[] cipher) throws Exception {
        int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
        return encodeSM2CipherToDER(mode, curveLength, SM3_DIGEST_LENGTH, cipher);
    }

    /**
     * DER編碼密文
     *
     * @param curveLength  曲線長度,SM2的話就是256位。
     * @param digestLength 摘要長度,如果是SM2的話因為默認使用SM3摘要,SM3摘要長度為32字節。
     * @param cipher       默認輸入C1C3C2順序的密文。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return 默認輸出按C1C3C2編碼的結果
     * @throws IOException
     */
    public static byte[] encodeSM2CipherToDER(int curveLength, int digestLength, byte[] cipher)
            throws Exception {
        return encodeSM2CipherToDER(Mode.C1C3C2, curveLength, digestLength, cipher);
    }

    /**
     * @param mode         指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2
     * @param curveLength  曲線長度,SM2的話就是256位。
     * @param digestLength 摘要長度,如果是SM2的話因為默認使用SM3摘要,SM3摘要長度為32字節。
     * @param cipher       根據mode不同,需要輸入的密文C1C2C3排列順序不同。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @return 按指定mode DER編碼后的密文
     * @throws Exception
     */
    public static byte[] encodeSM2CipherToDER(Mode mode, int curveLength, int digestLength, byte[] cipher)
            throws Exception {

        byte[] c1x = new byte[curveLength];
        byte[] c1y = new byte[curveLength];
        byte[] c2 = new byte[cipher.length - c1x.length - c1y.length - 1 - digestLength];
        byte[] c3 = new byte[digestLength];

        int startPos = 1;
        System.arraycopy(cipher, startPos, c1x, 0, c1x.length);
        startPos += c1x.length;
        System.arraycopy(cipher, startPos, c1y, 0, c1y.length);
        startPos += c1y.length;
        if (mode == Mode.C1C2C3) {
            System.arraycopy(cipher, startPos, c2, 0, c2.length);
            startPos += c2.length;
            System.arraycopy(cipher, startPos, c3, 0, c3.length);
        } else if (mode == Mode.C1C3C2) {
            System.arraycopy(cipher, startPos, c3, 0, c3.length);
            startPos += c3.length;
            System.arraycopy(cipher, startPos, c2, 0, c2.length);
        } else {
            throw new Exception("Unsupported mode:" + mode);
        }

        ASN1Encodable[] arr = new ASN1Encodable[4];
        arr[0] = new ASN1Integer(c1x);
        arr[1] = new ASN1Integer(c1y);
        if (mode == Mode.C1C2C3) {
            arr[2] = new DEROctetString(c2);
            arr[3] = new DEROctetString(c3);
        } else {
            arr[2] = new DEROctetString(c3);
            arr[3] = new DEROctetString(c2);
        }
        DERSequence ds = new DERSequence(arr);
        return ds.getEncoded(ASN1Encoding.DER);
    }

    /**
     * 解碼DER密文
     *
     * @param derCipher 默認輸入按C1C3C2順序DER編碼的密文
     * @return 輸出按C1C3C2排列的字節數組,C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     */
    public static byte[] decodeDERSM2Cipher(byte[] derCipher) throws Exception {
        return decodeDERSM2Cipher(Mode.C1C3C2, derCipher);
    }

    /**
     * @param mode      指定密文結構,舊標准的為C1C2C3,新的[《SM2密碼算法使用規范》 GM/T 0009-2012]標准為C1C3C2
     * @param derCipher 根據mode輸入C1C2C3或C1C3C2順序DER編碼后的密文
     * @return 根據mode不同,輸出的密文C1C2C3排列順序不同。C1為65字節第1字節為壓縮標識,這里固定為0x04,后面64字節為xy分量各32字節。C3為32字節。C2長度與原文一致。
     * @throws Exception
     */
    public static byte[] decodeDERSM2Cipher(Mode mode, byte[] derCipher) throws Exception {
        ASN1Sequence as = DERSequence.getInstance(derCipher);
        byte[] c1x = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
        byte[] c1y = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
        byte[] c3;
        byte[] c2;
        if (mode == Mode.C1C2C3) {
            c2 = ((DEROctetString) as.getObjectAt(2)).getOctets();
            c3 = ((DEROctetString) as.getObjectAt(3)).getOctets();
        } else if (mode == Mode.C1C3C2) {
            c3 = ((DEROctetString) as.getObjectAt(2)).getOctets();
            c2 = ((DEROctetString) as.getObjectAt(3)).getOctets();
        } else {
            throw new Exception("Unsupported mode:" + mode);
        }

        int pos = 0;
        byte[] cipherText = new byte[1 + c1x.length + c1y.length + c2.length + c3.length];
        final byte uncompressedFlag = 0x04;
        cipherText[0] = uncompressedFlag;
        pos += 1;
        System.arraycopy(c1x, 0, cipherText, pos, c1x.length);
        pos += c1x.length;
        System.arraycopy(c1y, 0, cipherText, pos, c1y.length);
        pos += c1y.length;
        if (mode == Mode.C1C2C3) {
            System.arraycopy(c2, 0, cipherText, pos, c2.length);
            pos += c2.length;
            System.arraycopy(c3, 0, cipherText, pos, c3.length);
        } else if (mode == Mode.C1C3C2) {
            System.arraycopy(c3, 0, cipherText, pos, c3.length);
            pos += c3.length;
            System.arraycopy(c2, 0, cipherText, pos, c2.length);
        }
        return cipherText;
    }

    /**
     * 簽名
     *
     * @param priKey  私鑰
     * @param srcData 原文
     * @return DER編碼后的簽名值
     * @throws CryptoException
     */
    public static byte[] sign(BCECPrivateKey priKey, byte[] srcData) throws CryptoException {
        ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
        return sign(priKeyParameters, null, srcData);
    }

    /**
     * 簽名
     * 不指定withId,則默認withId為字節數組:"1234567812345678".getBytes()
     *
     * @param priKeyParameters 私鑰
     * @param srcData          原文
     * @return DER編碼后的簽名值
     * @throws CryptoException
     */
    public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] srcData) throws CryptoException {
        return sign(priKeyParameters, null, srcData);
    }

    /**
     * 私鑰簽名
     *
     * @param priKey  私鑰
     * @param withId  可以為null,若為null,則默認withId為字節數組:"1234567812345678".getBytes()
     * @param srcData 原文
     * @return DER編碼后的簽名值
     * @throws CryptoException
     */
    public static byte[] sign(BCECPrivateKey priKey, byte[] withId, byte[] srcData) throws CryptoException {
        ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
        return sign(priKeyParameters, withId, srcData);
    }

    /**
     * 簽名
     *
     * @param priKeyParameters 私鑰
     * @param withId           可以為null,若為null,則默認withId為字節數組:"1234567812345678".getBytes()
     * @param srcData          源數據
     * @return DER編碼后的簽名值
     * @throws CryptoException
     */
    public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] withId, byte[] srcData)
            throws CryptoException {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = null;
        ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom());
        if (withId != null) {
            param = new ParametersWithID(pwr, withId);
        } else {
            param = pwr;
        }
        signer.init(true, param);
        signer.update(srcData, 0, srcData.length);
        return signer.generateSignature();
    }

    /**
     * 將DER編碼的SM2簽名解碼成64字節的純R+S字節流
     *
     * @param derSign
     * @return 64字節數組,前32字節為R,后32字節為S
     */
    public static byte[] decodeDERSM2Sign(byte[] derSign) {
        ASN1Sequence as = DERSequence.getInstance(derSign);
        byte[] rBytes = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
        byte[] sBytes = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
        //由於大數的補0規則,所以可能會出現33個字節的情況,要修正回32個字節
        rBytes = fixToCurveLengthBytes(rBytes);
        sBytes = fixToCurveLengthBytes(sBytes);
        byte[] rawSign = new byte[rBytes.length + sBytes.length];
        System.arraycopy(rBytes, 0, rawSign, 0, rBytes.length);
        System.arraycopy(sBytes, 0, rawSign, rBytes.length, sBytes.length);
        return rawSign;
    }

    /**
     * 把64字節的純R+S字節數組編碼成DER編碼
     *
     * @param rawSign 64字節數組形式的SM2簽名值,前32字節為R,后32字節為S
     * @return DER編碼后的SM2簽名值
     * @throws IOException
     */
    public static byte[] encodeSM2SignToDER(byte[] rawSign) throws IOException {
        //要保證大數是正數
        BigInteger r = new BigInteger(1, extractBytes(rawSign, 0, 32));
        BigInteger s = new BigInteger(1, extractBytes(rawSign, 32, 32));
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(r));
        v.add(new ASN1Integer(s));
        return new DERSequence(v).getEncoded(ASN1Encoding.DER);
    }

    /**
     * 驗簽
     *
     * @param pubKey  公鑰
     * @param srcData 原文
     * @param sign    DER編碼的簽名值
     * @return
     */
    public static boolean verify(BCECPublicKey pubKey, byte[] srcData, byte[] sign) {
        ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
        return verify(pubKeyParameters, null, srcData, sign);
    }

    /**
     * 驗簽
     * 不指定withId,則默認withId為字節數組:"1234567812345678".getBytes()
     *
     * @param pubKeyParameters 公鑰
     * @param srcData          原文
     * @param sign             DER編碼的簽名值
     * @return 驗簽成功返回true,失敗返回false
     */
    public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] srcData, byte[] sign) {
        return verify(pubKeyParameters, null, srcData, sign);
    }

    /**
     * 驗簽
     *
     * @param pubKey  公鑰
     * @param withId  可以為null,若為null,則默認withId為字節數組:"1234567812345678".getBytes()
     * @param srcData 原文
     * @param sign    DER編碼的簽名值
     * @return
     */
    public static boolean verify(BCECPublicKey pubKey, byte[] withId, byte[] srcData, byte[] sign) {
        ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
        return verify(pubKeyParameters, withId, srcData, sign);
    }

    /**
     * 驗簽
     *
     * @param pubKeyParameters 公鑰
     * @param withId           可以為null,若為null,則默認withId為字節數組:"1234567812345678".getBytes()
     * @param srcData          原文
     * @param sign             DER編碼的簽名值
     * @return 驗簽成功返回true,失敗返回false
     */
    public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] withId, byte[] srcData, byte[] sign) {
        SM2Signer signer = new SM2Signer();
        CipherParameters param;
        if (withId != null) {
            param = new ParametersWithID(pubKeyParameters, withId);
        } else {
            param = pubKeyParameters;
        }
        signer.init(false, param);
        signer.update(srcData, 0, srcData.length);
        return signer.verifySignature(sign);
    }

    private static byte[] extractBytes(byte[] src, int offset, int length) {
        byte[] result = new byte[length];
        System.arraycopy(src, offset, result, 0, result.length);
        return result;
    }

    private static byte[] fixToCurveLengthBytes(byte[] src) {
        if (src.length == CURVE_LEN) {
            return src;
        }

        byte[] result = new byte[CURVE_LEN];
        if (src.length > CURVE_LEN) {
            System.arraycopy(src, src.length - result.length, result, 0, result.length);
        } else {
            System.arraycopy(src, 0, result, result.length - src.length, src.length);
        }
        return result;
    }
}


  • 最后寫個測試類


import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import java.security.Security;

/**
 * @Auther: lin
 * @Date: 2021/03/05/14:34
 * @Description:
 */
public class Testsm2 {
    public static void main(String[] args) {
        {
            try {

                byte[] pkcs12 = FileUtil.readFile("D:/123456.pfx");
                Security.addProvider(new BouncyCastleProvider());
                BCECPublicKey  pubKey = SM2CertUtil.getPublicKeyFromPfx(pkcs12,  "111111");
                BCECPrivateKey priKey = SM2CertUtil.getPrivateKeyFromPfx(pkcs12, "111111");
                String s2= "BB+brzw1mApr0hV447nL3eNmLnWcD4FswR3SeQaKwt/m3A1i9abvRb6Zys0TtJsLXgDlJxdveeHbMmvrfqEqWtE=vv3VVkZog1XzX2GapKHHYbBjKndPS7o4HdbO8G6aXvy9OpSnSy0hG8RZM8OcUuR0X4UIUmf0+cSxjQSxL2nBKLNcq7vlTCv4qtZBe/zFvinPt7VbzOVuYK+EMKiqnA==+WX9nIjlqL7oSbjd3DJWN8G7Z4LTdXMf85PY7k6UPt0=\n";
                String s1= "BHjdSeLfgc/K8Od6CV4+KzefP9/d3ojzG5oyojVaBSJLooedGxQC5lAopHziS9LYet2p5i3rMn+njYOXaZOdKZ8=Gc0WL+s5jR60Qv0EZuPtvvqzBdRD+nYnxSLraqxcMOpDpzqYMIXMt5w/jbClIjBHIjEQd9/K5B9b0tyxOjNZ9NHyM6QAN9LJiGqez6FHNIoY1NPoH4chGt5lbLguEf9KMBNCCg119H/+lx4Ple76QCjDEU8/lJHWq2w=\n";

                System.out.println("Pri Hex:"
                        + ByteUtils.toHexString(priKey.getD().toByteArray()).toUpperCase());
                System.out.println("Pub X Hex:"
                        + ByteUtils.toHexString(pubKey.getQ().getAffineXCoord().getEncoded()).toUpperCase());
                System.out.println("Pub X Hex:"
                        + ByteUtils.toHexString(pubKey.getQ().getAffineYCoord().getEncoded()).toUpperCase());
                System.out.println("Pub Point Hex:"
                        + ByteUtils.toHexString(pubKey.getQ().getEncoded(false)).toUpperCase());


                byte[] decryptedData1 = SM2Util.decrypt(SM2Engine.Mode.C1C2C3, priKey, Base64Utils.decode(s1));
                System.out.println("The first  SM2 decrypt result:\n" +new String(decryptedData1));

                byte[] decryptedData2 = SM2Util.decrypt(SM2Engine.Mode.C1C2C3, priKey, Base64Utils.decode(s2));
                System.out.println("The second SM2 decrypt result:\n" +new String(decryptedData2));

            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
}

執行結果

Pri Hex:00E7E5F1A9BEB2FE01FF452B469B073DB404C77FA32E5694EEF95AC1E135679F9B
Pub X Hex:98E24B18217BB895B407AAA2FABB4E2C64702AC5B71CB59B77C56AF5E4768991
Pub X Hex:E2361B3CBC1B805F5B6FEE46C8F47020C6ED04F85075237E4C8F9AACD07510F5
Pub Point Hex:0498E24B18217BB895B407AAA2FABB4E2C64702AC5B71CB59B77C56AF5E4768991E2361B3CBC1B805F5B6FEE46C8F47020C6ED04F85075237E4C8F9AACD07510F5
The first  SM2 decrypt result:
公元2021年,公歷平年,共365天,53周。農歷辛丑年(牛年),無閏月
The second SM2 decrypt result:
2020年代的第2年,中華人民共和國成立72周年,中國XXX成立100周年。

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM