import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
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.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
public class SM2Utils {
private static final LoggerLOGGER = LoggerFactory.getLogger(SM2Utils.class);
/**
* @Description 生成秘钥对
* @Author zd
* @return KeyPair
*/
public static KeyPaircreateECKeyPair() {
//使用标准名称创建EC参数生成的参数规范
final ECGenParameterSpec sm2Spec =new ECGenParameterSpec("sm2p256v1");
// 获取一个椭圆曲线类型的密钥对生成器
final KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
// 使用SM2算法域参数集初始化密钥生成器(默认使用以最高优先级安装的提供者的 SecureRandom 的实现作为随机源)
// kpg.initialize(sm2Spec);
// 使用SM2的算法域参数集和指定的随机源初始化密钥生成器
kpg.initialize(sm2Spec, new SecureRandom());
// 通过密钥生成器生成密钥对
return kpg.generateKeyPair();
}catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 生成SM2公私钥对
* @return
*/
private static AsymmetricCipherKeyPairgenKeyPair0() {
//获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
//构造domain参数
ECDomainParameters domainParameters =new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN());
//1.创建密钥生成器
ECKeyPairGenerator keyPairGenerator =new ECKeyPairGenerator();
//2.初始化生成器,带上随机数
try {
keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
}catch (NoSuchAlgorithmException e) {
LOGGER.error("生成公私钥对时出现异常:", e);
// e.printStackTrace();
}
//3.生成密钥对
AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
return asymmetricCipherKeyPair;
}
/**
* @Description 公钥加密
* @Author zd
* @param publicKeyHex SM2十六进制公钥
* @param data 明文数据
* @return String
*/
public static Stringencrypt(String publicKeyHex, String data) {
return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, 1);
}
/**
* @Description 公钥加密
* @Author zd
* @param publicKey SM2公钥
* @param data 明文数据
* @param modeType 加密模式
* @return String
*/
public static Stringencrypt(BCECPublicKey publicKey, String data, int modeType) {
//加密模式
SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
if (modeType !=1) {
mode = SM2Engine.Mode.C1C2C3;
}
//通过公钥对象获取公钥的基本域参数。
ECParameterSpec ecParameterSpec = publicKey.getParameters();
ECDomainParameters ecDomainParameters =new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
//通过公钥值和公钥基本参数创建公钥参数对象
ECPublicKeyParameters ecPublicKeyParameters =new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
//根据加密模式实例化SM2公钥加密引擎
SM2Engine sm2Engine =new SM2Engine(mode);
//初始化加密引擎
sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
byte[] arrayOfBytes =null;
try {
//将明文字符串转换为指定编码的字节串
byte[] in = data.getBytes("utf-8");
//通过加密引擎对字节数串行加密
arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
}catch (Exception e) {
System.out.println("SM2加密时出现异常:" + e.getMessage());
e.printStackTrace();
}
//将加密后的字节串转换为十六进制字符串
return Hex.toHexString(arrayOfBytes);
}
/**
* @Description 私钥解密
* @Author zd
* @param privateKeyHex SM2十六进制私钥
* @param cipherData 密文数据
* @return String
*/
public static Stringdecrypt(String privateKeyHex, String cipherData) {
return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, 1);
}
/**
* @Description 私钥解密
* @Author zd
* @param privateKey SM私钥
* @param cipherData 密文数据
* @param modeType 解密模式
* @return
*/
public static Stringdecrypt(BCECPrivateKey privateKey, String cipherData, int modeType) {
//解密模式
SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
if (modeType !=1) {
mode = SM2Engine.Mode.C1C2C3;
}
//将十六进制字符串密文转换为字节数组(需要与加密一致,加密是:加密后的字节数组转换为了十六进制字符串)
byte[] cipherDataByte = Hex.decode(cipherData);
//通过私钥对象获取私钥的基本域参数。
ECParameterSpec ecParameterSpec = privateKey.getParameters();
ECDomainParameters ecDomainParameters =new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
//通过私钥值和私钥钥基本参数创建私钥参数对象
ECPrivateKeyParameters ecPrivateKeyParameters =new ECPrivateKeyParameters(privateKey.getD(),
ecDomainParameters);
//通过解密模式创建解密引擎并初始化
SM2Engine sm2Engine =new SM2Engine(mode);
sm2Engine.init(false, ecPrivateKeyParameters);
String result =null;
try {
//通过解密引擎对密文字节串进行解密
byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
//将解密后的字节串转换为utf8字符编码的字符串(需要与明文加密时字符串转换成字节串所指定的字符编码保持一致)
result =new String(arrayOfBytes, "utf-8");
}catch (Exception e) {
System.out.println("SM2解密时出现异常" + e.getMessage());
}
return result;
}
//椭圆曲线ECParameters ASN.1 结构
private static X9ECParametersx9ECParameters = GMNamedCurves.getByName("sm2p256v1");
//椭圆曲线公钥或私钥的基本域参数。
private static ECParameterSpececDomainParameters =new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
/**
* @Description 公钥字符串转换为 BCECPublicKey 公钥对象
* @Author zd
* @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04:表示该公钥为非压缩格式,操作时需要删除)
* @return BCECPublicKey SM2公钥对象
*/
public static BCECPublicKeygetECPublicKeyByPublicKeyHex(String pubKeyHex) {
//截取64字节有效的SM2公钥(如果公钥首个字节为0x04)
if (pubKeyHex.length() >128) {
pubKeyHex = pubKeyHex.substring(pubKeyHex.length() -128);
}
//将公钥拆分为x,y分量(各32字节)
String stringX = pubKeyHex.substring(0, 64);
String stringY = pubKeyHex.substring(stringX.length());
//将公钥x、y分量转换为BigInteger类型
BigInteger x =new BigInteger(stringX, 16);
BigInteger y =new BigInteger(stringY, 16);
//通过公钥x、y分量创建椭圆曲线公钥规范
ECPublicKeySpec ecPublicKeySpec =new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);
//通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)
return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
}
/**
* @Description 私钥字符串转换为 BCECPrivateKey 私钥对象
* @Author zd
* @param privateKeyHex 32字节十六进制私钥字符串
* @return BCECPrivateKey SM2私钥对象
*/
public static BCECPrivateKeygetBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
//将十六进制私钥字符串转换为BigInteger对象
BigInteger d =new BigInteger(privateKeyHex, 16);
//通过私钥和私钥域参数集创建椭圆曲线私钥规范
ECPrivateKeySpec ecPrivateKeySpec =new ECPrivateKeySpec(d, ecDomainParameters);
//通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)
return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
}
/**
* 私钥签名
* @param privateKey 私钥
* @param content 待签名内容
* @return
*/
public static Stringsign(String privateKey, String content)throws CryptoException {
//待签名内容转为字节数组
byte[] message = Hex.decode(content);
//获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
//构造domain参数
ECDomainParameters domainParameters =new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN());
BigInteger privateKeyD =new BigInteger(privateKey, 16);
ECPrivateKeyParameters privateKeyParameters =new ECPrivateKeyParameters(privateKeyD, domainParameters);
//创建签名实例
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) {
log.error("签名时出现异常:", e);
}
sm2Signer.update(message, 0, message.length);
//生成签名,签名分为两部分r和s,分别对应索引0和1的数组
byte[] signBytes = sm2Signer.generateSignature();
String sign = Hex.toHexString(signBytes);
return sign;
}
/**
* 验证签名
* @param publicKey 公钥
* @param content 待签名内容
* @param sign 签名值
* @return
*/
public static boolean verify(String publicKey, String content, String sign) {
//待签名内容
byte[] message = Hex.decode(content);
byte[] signData = Hex.decode(sign);
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
// 构造domain参数
ECDomainParameters domainParameters =new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(),
sm2ECParameters.getN());
//提取公钥点
ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.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;
}
public static void main(String[] args)throws UnsupportedEncodingException, CryptoException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
String data ="{\"head\":{\"reqCode\":\"1001\",\"reqDate\":\"20220321\",\"reqTime\":\"231815\",\"reqId\":\"2022\",\"bbkNbr\":\"379\"},\"body\":{\"regCode\":\"8238003300209049600\"}}";
String timestamp = String.valueOf(System.currentTimeMillis());
AsymmetricCipherKeyPair asymmetricCipherKeyPair =genKeyPair0();
//提取公钥点
ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
//公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
String publicStr = Hex.toHexString(ecPoint.getEncoded(false));
BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();
String privateStr = privatekey.toString(16);
System.out.println("---->生成公钥:" + publicStr);
System.out.println("---->生成私钥:" + privateStr);
String encryptData =encrypt(publicStr, data);
System.out.println("---->加密结果:" + encryptData);
String decryptData =decrypt(privateStr,encryptData);
System.out.println("---->解密结果:" + decryptData);
String sign = SM2Utils.sign(privateStr,Hex.toHexString((data + timestamp).getBytes()));
System.out.println("---->签名结果:" + sign);
boolean verify = SM2Utils.verify(publicStr, Hex.toHexString((data + timestamp).getBytes()), sign);
System.out.println("---->验签结果:" + verify);
}
}