赞
踩
用上一篇的命令生成公私钥对。
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.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,要去除
- 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 NoSuchAlgorithmException, InvalidAlgorithmParameterException {
-
- String M="encryption standard";
- System.out.println("\n明文:"+ M);
-
- SM2Util sm2 = new SM2Util();
- KeyPair keyPair = sm2.geneSM2KeyPair();
-
- PublicKey publicKey = keyPair.getPublic();
- String pubKeyHexString = sm2.getPubKeyHexString(publicKey);
- System.out.println("公钥:"+ pubKeyHexString);
-
- PrivateKey privateKey = keyPair.getPrivate();
- String priKeyHexString = sm2.getPriKeyHexString(privateKey);
- System.out.println("私钥:"+ priKeyHexString);
-
- String cipherData = sm2.encrypt(pubKeyHexString, M);
- System.out.println("密文:" + cipherData);
-
- String text=sm2.decrypt(priKeyHexString, cipherData);
- System.out.println("解密:"+text);
- }

参考资料:
支付对接常用的加密方式介绍以及java代码实现_京东云官方的技术博客_51CTO博客
对于如何从SM2的pfx证书文件中解析公钥和私钥,并从二次加密的密文中解密 - 旧信 - 博客园 (cnblogs.com)
GitHub - ZZMarquis/gmhelper: 基于BC库:国密SM2/SM3/SM4算法简单封装;实现SM2 X509v3证书的签发;实现SM2 pfx证书的签发
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。