首先呢,由于我的域名之前处理点问题,然后备案第二个网站时候,第一个网站没法访问,所以备案没过,阿里云告诉我要删除一个网站的备案,但是他没告诉我要删除主体,所以我的备案主体成了空壳主体,要传真或者发快递到郑州市金水区民航路8号河南省通信管理局一楼网站备案中心,或者等它自动注销。好气,所以我转腾讯云了,博客就重新写吧。

这个事情是有一次在 hutool 的issues去有人提出了一个 SM2 加密问题,那个问题的原因是对于某些需要接入硬件加密机和软加密的公钥可能不太一样,大概就是 公钥der编码问题 ,然后某天微信有个人加我问我是不是 CherryRum(GitHub) ,由于刚好过年没回家,比较无聊,后来才知道是个妹子(后来知道的),男人不能说不行,她给我了一个测试的pfx证书还有两段加密后的密文,然后没了,说没法解密,总是报错。但是证书可以用 hutool 封装的方法进行加解密。

  • 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=

    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"

    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工具类
    参考 gayhub https://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周年。