用上一篇的命令生成公私钥对。
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证书的签发