sm2证书生成(openssl生成公私钥对)—使用

用上一篇的命令生成公私钥对。

1、sm2PubKey.pem

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEmxmSy4HOD2d2sakaJTw0QFhRGZs2
5umcKzmg12FAsYNjVRmtLxcbydzTMELGKpHHle//IZ0Eqx7P15IKiyoK/g==
-----END PUBLIC KEY-----

2、sm2PriKeyPkcs8.pem

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgilIj1dWX+977jcQO
oHDQq6XtbWaazcZdJiHjXpAhKD6hRANCAASbGZLLgc4PZ3axqRolPDRAWFEZmzbm
6ZwrOaDXYUCxg2NVGa0vFxvJ3NMwQsYqkceV7/8hnQSrHs/XkgqLKgr+
-----END PRIVATE KEY-----

3、openssl ec -in sm2PriKeyPkcs8.pem -text

read EC key
Private-Key: (256 bit)
priv:
    8a:52:23:d5:d5:97:fb:de:fb:8d:c4:0e:a0:70:d0:
    ab:a5:ed:6d:66:9a:cd:c6:5d:26:21:e3:5e:90:21:
    28:3e
pub:
    04:9b:19:92:cb:81:ce:0f:67:76:b1:a9:1a:25:3c:
    34:40:58:51:19:9b:36:e6:e9:9c:2b:39:a0:d7:61:
    40:b1:83:63:55:19:ad:2f:17:1b:c9:dc:d3:30:42:
    c6:2a:91:c7:95:ef:ff:21:9d:04:ab:1e:cf:d7:92:
    0a:8b:2a:0a:fe
ASN1 OID: SM2
writing EC key
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIpSI9XVl/ve+43EDqBw0Kul7W1mms3GXSYh416QISg+oAoGCCqBHM9V
AYItoUQDQgAEmxmSy4HOD2d2sakaJTw0QFhRGZs25umcKzmg12FAsYNjVRmtLxcb
ydzTMELGKpHHle//IZ0Eqx7P15IKiyoK/g==
-----END EC PRIVATE KEY-----

5、工具类
BCECUtil.java

package GMSM;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
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.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
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();
        }
    }
}

SM2Util.java

package GMSM;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
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.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.EllipticCurve;

/**
 * SM2 工具类
 */
public class SM2Util {
	
	static {
        Security.addProvider(new BouncyCastleProvider());
    }
	
    //
    /*
     * 以下为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());

    //

	
	// SM2推荐曲线名称
	public static final String SM2_CURVE_NAME = "sm2p256v1";

	public static final Charset UTF_8 = Charset.forName("utf-8");

	/**
	 * 生成密钥
	 *
	 * @return
	 * @throws Exception
	 */
	public static KeyPair genKeyPair() throws Exception {
		final ECGenParameterSpec sm2Spec = new ECGenParameterSpec(SM2_CURVE_NAME);
		// 获取一个椭圆曲线类型的密钥对生成器
		final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
		SecureRandom random = new SecureRandom();
		// 使用SM2的算法区域初始化密钥生成器
		kpg.initialize(sm2Spec, random);
		// 获取密钥对
		KeyPair keyPair = kpg.generateKeyPair();

		return keyPair;
	}

	/**
	 * SM2根据公钥加密 param: message 待加密内容 , publicKey 加密公钥(BASE64编码) return:
	 * 加密信息的Base64编码
	 * 
	 * @throws InvalidCipherTextException
	 */
	public static String encryptBySM2(String message, String publicKey) throws InvalidCipherTextException {
		ECDomainParameters domin = getDomain();
		// 公钥对象
		ECPublicKeyParameters pubKeyParameters = getPubKey(publicKey, domin);

		byte[] cipherBytes = new byte[0];

		cipherBytes = encrypt(SM2Engine.Mode.C1C3C2, pubKeyParameters, message.getBytes(UTF_8));

		return Base64.toBase64String(cipherBytes);
	}

	/**
	 * SM2根据私钥解密 param: cipherText 待解密密文 privateKey-私钥(BASE64编码)
	 */
	public static String decryptBySM2(String cipherText, String privateKey) throws InvalidCipherTextException {
		BigInteger d = new BigInteger(1, Base64.decode(privateKey));
		ECDomainParameters domin = getDomain();
		// 私钥对象
		ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(d, domin);
		byte[] decrypt = decrypt(SM2Engine.Mode.C1C3C2, ecPrivateKeyParameters, Base64.decode(cipherText));
		return new String(decrypt, UTF_8);
	}

	/**
	 * 根据公钥字符串创建公钥对象
	 *
	 */
	public static ECPublicKeyParameters getPubKey(String publicKey, ECDomainParameters domain) {
		ECCurve curve = domain.getCurve();
		ECPoint point = curve.decodePoint(Base64.decode(publicKey));
		ECPublicKeyParameters PublicKey = new ECPublicKeyParameters(point, domain);
		return PublicKey;
	}

	/**
	 * @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(SM2Engine.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 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(SM2Engine.Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
			throws InvalidCipherTextException {
		SM2Engine engine = new SM2Engine(mode);
		engine.init(false, priKeyParameters);
		return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
	}

	public static ECDomainParameters getDomain() {
		// 获取一条SM2曲线参数
		X9ECParameters x9ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
		// 构造domain参数
		ECDomainParameters domain = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(),
				x9ECParameters.getN(), x9ECParameters.getH());
		return domain;
	}

	/**
	 * 私钥签名
	 * 
	 * @param privateKey 私钥
	 * @param content    待签名内容
	 * @return
	 */
	public static String sign(String privateKey, String content) throws CryptoException, CryptoException {
		// 待签名内容转为字节数组
		byte[] message = content.getBytes();

		BigInteger domainParameters = new BigInteger(1, Base64.decode(privateKey));
		ECDomainParameters domin = getDomain();

		// 私钥对象
		ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(domainParameters, domin);

		// 创建签名实例
		SM2Signer sm2Signer = new SM2Signer();

		// 初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678
		try {
			sm2Signer.init(true,
					new ParametersWithID(
							new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")),
							Strings.toByteArray("1234567812345678")));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		sm2Signer.update(message, 0, message.length);
		// 生成签名,签名分为两部分r和s,分别对应索引0和1的数组
		byte[] signBytes = sm2Signer.generateSignature();

		String sign = Base64.toBase64String(signBytes);

		return sign;
	}

	/**
	 * 验证签名
	 * 
	 * @param publicKey 公钥
	 * @param content   待签名内容
	 * @param sign      签名值
	 * @return
	 */
	public static boolean verify(String publicKey, String content, String sign) {
		// 待签名内容
		byte[] message = content.getBytes();
		byte[] signData = Base64.decode(sign);

		// 获取一条SM2曲线参数
		X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
		// 构造domain参数
		ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(),
				sm2ECParameters.getN());
		// 提取公钥点
		ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(java.util.Base64.getDecoder().decode(publicKey));
		// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
		ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
		// 创建签名实例
		SM2Signer sm2Signer = new SM2Signer();
		ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters,
				Strings.toByteArray("1234567812345678"));
		sm2Signer.init(false, parametersWithID);
		sm2Signer.update(message, 0, message.length);
		// 验证签名结果
		boolean verify = sm2Signer.verifySignature(signData);
		return verify;
	}
	
    /**
     * 获取私钥(16进制字符串,头部不带00长度共64)
     * @param privateKey 私钥
     * @return
     */
    public static String getPriKeyHexString(PrivateKey privateKey){
        BCECPrivateKey s=(BCECPrivateKey)privateKey;
        String priKeyHexString = Hex.toHexString(s.getD().toByteArray());
        if(null!= priKeyHexString && priKeyHexString.length()==66 && "00".equals(priKeyHexString.substring(0,2))){
            return priKeyHexString.substring(2);
        }
        return priKeyHexString;
    }

    /**
     * 获取公钥(16进制字符串,头部带04长度共130)
     * @param publicKey
     * @return
     */
    public static String getPubKeyHexString(PublicKey publicKey){
        BCECPublicKey p=(BCECPublicKey)publicKey;
        return Hex.toHexString(p.getQ().getEncoded(false));
    }
    
}

6、写一个main方法

public static void main(String[] args) throws Exception {
		
		//openssl生成sm2公私钥对
		//公钥
		String pu1 = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEmxmSy4HOD2d2sakaJTw0QFhRGZs25umcKzmg12FAsYNjVRmtLxcbydzTMELGKpHHle//IZ0Eqx7P15IKiyoK/g==";
		//私钥
		String pr1 = "MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgilIj1dWX+977jcQOoHDQq6XtbWaazcZdJiHjXpAhKD6hRANCAASbGZLLgc4PZ3axqRolPDRAWFEZmzbm6ZwrOaDXYUCxg2NVGa0vFxvJ3NMwQsYqkceV7/8hnQSrHs/XkgqLKgr+";
		
		//公钥文件是x509格式,转换成ECPublicKey,提取真正的公钥
		byte[] x509pu1 = Base64.decode(pu1);
		BCECPublicKey bcecPublicKey = BCECUtil.convertX509ToECPublicKey(x509pu1);
		String s1 = ByteUtils.toHexString(bcecPublicKey.getQ().getEncoded(false));
		System.out.println("公钥:" + s1);
		
		//私钥文件是pkcs8格式,转换成ECPrivateKey,提取真正的私钥
		byte[] x509pr1 = Base64.decode(pr1);
		BCECPrivateKey bcecPrivateKey = BCECUtil.convertPKCS8ToECPrivateKey(x509pr1);
		String s2 = ByteUtils.toHexString(bcecPrivateKey.getD().toByteArray());
		//java会在私钥前加00,要去除
		if (s2.startsWith("00")) {
		s2 = s2.substring(2);
		}
		System.out.println("私钥:" + s2);
		
		System.out.println("==========测试加密解密==========");
		
		//String publicKey = "049b1992cb81ce0f6776b1a91a253c34405851199b36e6e99c2b39a0d76140b183635519ad2f171bc9dcd33042c62a91c795efff219d04ab1ecfd7920a8b2a0afe";
		String publicKey = s1;
		String str = "11111111111111111111111111111111111111111111111111111111111";
		String en = encryptBySM2(str, Base64.toBase64String(Hex.decode(publicKey)) );
		System.out.println("加密后:" + en);
		//String privateKey = "8a5223d5d597fbdefb8dc40ea070d0aba5ed6d669acdc65d2621e35e9021283e";
		String privateKey = s2;
		String de = decryptBySM2(en, Base64.toBase64String(Hex.decode(privateKey)) );
		System.out.println("解密后:" + de);
		
		System.out.println("==========测试签名验签==========");
		
		String sign = sign(Base64.toBase64String(Hex.decode(privateKey)), str);
		System.out.println("sign:" + sign);
		boolean flag = verify(Base64.toBase64String(Hex.decode(publicKey)), str, sign);
		System.out.println("验签结果:" + flag);
	}

执行结果:

公钥:049b1992cb81ce0f6776b1a91a253c34405851199b36e6e99c2b39a0d76140b183635519ad2f171bc9dcd33042c62a91c795efff219d04ab1ecfd7920a8b2a0afe
私钥:8a5223d5d597fbdefb8dc40ea070d0aba5ed6d669acdc65d2621e35e9021283e
==========测试加密解密==========
加密后:BKIe16SRgAgHGgOalBDv1xtIONHVQtrfCXpnVnH2iOwhKoXXYLCDrp8FlcNtJwnpJxtF9soslWvO+HhfpmW4knRBGk+NLs9ftf+mSeNaVkFG2ySt0tIjUKHl51GUZlkW1yQ/DRm7w9BNheJgow+PX5jorKhzM1IIv2AshlNFQg9Yut066j+5dLtE+no5/32O9+4dP80z6jGc+FcI
解密后:11111111111111111111111111111111111111111111111111111111111
==========测试签名验签==========
sign:MEUCICKGZZ5X1JAFa40/vtqD4de+HKh9rAiWaAtiUKVAODPgAiEAkXcnA3OxcXk0latkmmr3KS5ptwBI6PiPVJcyJJrnSp8=
验签结果:true

密钥和命令解析出来的一样。
依赖包:bcprov-jdk15on-1.70.jar

SM2加密算法的结果长度,取决于明文长度,没记错的话是C1+C2+C3, C1和C3是定长,分别为64字节和32字节,C2的长度等于明文长度
准确的计算过程,可以看看GM/T 0003.4

用密钥明文16进制转16进制字符串再base64编码:

    public static void main(String[] args) throws Exception {
    	//公钥明文字符串转base64编码,公钥明文字符串128字节长度不带04
		String pu2 = "OWIxOTkyY2I4MWNlMGY2Nzc2YjFhOTFhMjUzYzM0NDA1ODUxMTk5YjM2ZTZlOTljMmIzOWEwZDc2MTQwYjE4MzYzNTUxOWFkMmYxNzFiYzlkY2QzMzA0MmM2MmE5MWM3OTVlZmZmMjE5ZDA0YWIxZWNmZDc5MjBhOGIyYTBhZmU=";
		//私钥明文字符串转base64编码,私钥明文字符串64字节长度
		String pr2 = "OGE1MjIzZDVkNTk3ZmJkZWZiOGRjNDBlYTA3MGQwYWJhNWVkNmQ2NjlhY2RjNjVkMjYyMWUzNWU5MDIxMjgzZQ==";
		
		//base64解码
		String s3 = "04" + new String(Base64.decode(pu2)); //公钥前加04
		String s4 = new String(Base64.decode(pr2));
		System.out.println("公钥:" + s3);
		System.out.println("私钥:" + s4);
		
		byte pub2[] = Hex.decode("04" + new String(Base64.decode(pu2))); 
		byte pri2[] = Hex.decode(new String(Base64.decode(pr2)));
		
		String str = "11111111111111111111111111111111111111111111111111111111111";
		
		System.out.println("==========测试加密解密==========");
		
		String en = encryptBySM2(str, Base64.toBase64String(pub2));
		System.out.println("加密后:" + en);
		String de = decryptBySM2(en, Base64.toBase64String(pri2));
		System.out.println("解密后:" + de);
		
		System.out.println("==========测试签名验签==========");
		
		String sign = sign(Base64.toBase64String(pri2), str);
		System.out.println("sign:" + sign);
		boolean flag = verify(Base64.toBase64String(pub2), str, sign);
		System.out.println("验签结果:" + flag);
		
	}

执行结果:

公钥:049b1992cb81ce0f6776b1a91a253c34405851199b36e6e99c2b39a0d76140b183635519ad2f171bc9dcd33042c62a91c795efff219d04ab1ecfd7920a8b2a0afe
私钥:8a5223d5d597fbdefb8dc40ea070d0aba5ed6d669acdc65d2621e35e9021283e
==========测试加密解密==========
加密后:BIPG2WfWmBz+xU/btAI796VF7RX53WYZfsujS2ICC5umdS7x6hIE+zDEJpStVY/rAYmJeqEsmNx2oHN/8rbWiDgkZJS2xZL50br+a2RThzmh2fcdFSy3MtNcvcFGB/FD/h+pm5ImdPO94HD9Efv3apNum3/V5sNxUOM08YMgXrIYx30osZzBE41NSfO7El2NM5Ryy7xj0qgPmq3j
解密后:11111111111111111111111111111111111111111111111111111111111
==========测试签名验签==========
sign:MEUCIQDTzmVPjkPqPvpV2hMJvqPIMNjakS5/iMY5aeIGIYjGAAIgOREKqHSC0bfb1RIbhGz80wKpU87qqFYYCxyjiI64CRg=
验签结果:true

用工具类生成密钥对:

public static void main(String[] args) throws Exception {
    	 
        String M = "encryption standard";
        System.out.println("\n明文:"+ M);
 
        KeyPair keyPair = SM2Util.genKeyPair();
 
        PublicKey publicKey = keyPair.getPublic();
        String pubKeyHexString = SM2Util.getPubKeyHexString(publicKey);
        System.out.println("公钥:"+ pubKeyHexString);
 
        PrivateKey privateKey = keyPair.getPrivate();
        String priKeyHexString = SM2Util.getPriKeyHexString(privateKey);
        System.out.println("私钥:"+ priKeyHexString);
 
        String cipherData = SM2Util.encryptBySM2(M, Base64.toBase64String(Hex.decode(pubKeyHexString)));
        System.out.println("密文:" + cipherData);
 
        String text = SM2Util.decryptBySM2(cipherData, Base64.toBase64String(Hex.decode(priKeyHexString)));
        System.out.println("解密:"+text);
    }

执行结果:

明文:encryption standard
公钥:049ea8abdf0b2b4c1f437d3e9203d77c3b795d608e7b4688de7a9b6bfa7024615af979256efe4b4f8cead3ce836bfc973df05c1f2eb92477d596b40c59d2805573
私钥:c7f84ede23b8035d716225200940d2e0f58258342aec2a8806ece7fe4c898a37
密文:BMPJMj+R5bbo0Jasyi7OCiLT8X6N6OgLNHnevoUUcWnc425HVsnHLd6wsijYNhj2ds1thsQzSSRbYNzq2fgfPUcP9EHo6CLO12i5S5KDEvGqqmI8uWmjaHfFo3mA2McUxBNwk8YZK3yI5DGEY2819t9VzKw=
解密:encryption standard

参考资料:
支付对接常用的加密方式介绍以及java代码实现_京东云官方的技术博客_51CTO博客

对于如何从SM2的pfx证书文件中解析公钥和私钥,并从二次加密的密文中解密 - 旧信 - 博客园 (cnblogs.com)

GitHub - ZZMarquis/gmhelper: 基于BC库:国密SM2/SM3/SM4算法简单封装;实现SM2 X509v3证书的签发;实现SM2 pfx证书的签发

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值