新增sm加解密

This commit is contained in:
zhuyijun 2022-11-18 20:55:09 +08:00
parent 4254be931d
commit e01a21f652
8 changed files with 982 additions and 39 deletions

View File

@ -1,6 +1,5 @@
package cn.zyjblogs.server.demo; package cn.zyjblogs.server.demo;
import cn.zyjblogs.starter.common.utils.crypto.Sm2Utils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -20,20 +19,4 @@ public class DemoController {
public String success() { public String success() {
return "登录成功"; return "登录成功";
} }
public static void main(String[] args) {
String data = "9b758fceca85fb0ddbaaf649ad9f877f2ac8d0c5752228833aa2b58a5f6ecfab0d074cf1e0fdae5ab63b61b8a67182fb547f64ba76684a71240cb8c7a025efa230880fea071315048a0563bd27109c5a2c76b0b56b05424eb9555f7d13142437260f3bbf61152ab31078b6965a11705a8ec807d257c0fecd906d7a2f87e8f1cb717a";
String pub = "0417f347d7fa08ae6ad9bf8ef6ac6c313810e05044290f7c18dc9b913b252603505cf7cdbf7ac7d88de508e78bbc2d74cb28c0a90724ed4b751cc69bdfe55b68de";
String pri = "73d76cf4f553535d6ec45478fb1581baa0c83e166b347af10ab129966d3f187f";
try {
String decrypt = Sm2Utils.encrypt(pri, data, true);
System.out.println(decrypt);
String encrypt = Sm2Utils.encrypt(pub, decrypt, true);
System.out.println(encrypt);
String decrypt1 = Sm2Utils.decrypt(pri, encrypt, true);
System.out.println(decrypt1);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} }

View File

@ -5,16 +5,6 @@
package cn.zyjblogs.starter.common.utils.crypto; package cn.zyjblogs.starter.common.utils.crypto;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;
import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers; import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ECParameters;
@ -33,6 +23,12 @@ import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;
public class Sm2Utils { public class Sm2Utils {
private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1"); private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
private static byte SM2_CIPHER_FIRST_BIT = 4; private static byte SM2_CIPHER_FIRST_BIT = 4;
@ -42,16 +38,15 @@ public class Sm2Utils {
} }
public static String encrypt(String publicKey, String data) throws Exception { public static String encrypt(String publicKey, String data) throws Exception {
return encrypt(publicKey, data, true); return encrypt(publicKey, data, Mode.C1C3C2);
} }
public static String encrypt(String publicKey, String data, boolean isNew) throws Exception { public static String encrypt(String publicKey, String data, SM2Engine.Mode mode) throws Exception {
BCECPublicKey bcecPublicKey = getPublicKey(publicKey); BCECPublicKey bcecPublicKey = getPublicKey(publicKey);
ECParameterSpec parameters = bcecPublicKey.getParameters(); ECParameterSpec parameters = bcecPublicKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(parameters.getCurve(), parameters.getG(), parameters.getN()); ECDomainParameters ecDomainParameters = new ECDomainParameters(parameters.getCurve(), parameters.getG(), parameters.getN());
ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters); ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters);
SM2Engine.Mode model = isNew ? Mode.C1C3C2 : Mode.C1C2C3; SM2Engine sm2Engine = new SM2Engine(mode);
SM2Engine sm2Engine = new SM2Engine(model);
sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom())); sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
byte[] in = data.getBytes(StandardCharsets.UTF_8); byte[] in = data.getBytes(StandardCharsets.UTF_8);
byte[] bytes = sm2Engine.processBlock(in, 0, in.length); byte[] bytes = sm2Engine.processBlock(in, 0, in.length);
@ -60,18 +55,17 @@ public class Sm2Utils {
} }
public static String decrypt(String privateKey, String data) throws Exception { public static String decrypt(String privateKey, String data) throws Exception {
return decrypt(privateKey, data, true); return decrypt(privateKey, data, Mode.C1C3C2);
} }
public static String decrypt(String privateKey, String data, boolean isNew) throws Exception { public static String decrypt(String privateKey, String data, SM2Engine.Mode mode) throws Exception {
byte[] base64Decode = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8)); byte[] base64Decode = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8));
byte[] finalByte = addBitIfNeed(base64Decode); byte[] finalByte = addBitIfNeed(base64Decode);
BCECPrivateKey bcecPrivateKey = getPrivateKey(privateKey); BCECPrivateKey bcecPrivateKey = getPrivateKey(privateKey);
ECParameterSpec parameters = bcecPrivateKey.getParameters(); ECParameterSpec parameters = bcecPrivateKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(parameters.getCurve(), parameters.getG(), parameters.getN()); ECDomainParameters ecDomainParameters = new ECDomainParameters(parameters.getCurve(), parameters.getG(), parameters.getN());
ECPrivateKeyParameters ecPublicKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(), ecDomainParameters); ECPrivateKeyParameters ecPublicKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(), ecDomainParameters);
SM2Engine.Mode model = isNew ? Mode.C1C3C2 : Mode.C1C2C3; SM2Engine sm2Engine = new SM2Engine(mode);
SM2Engine sm2Engine = new SM2Engine(model);
sm2Engine.init(false, ecPublicKeyParameters); sm2Engine.init(false, ecPublicKeyParameters);
byte[] bytes = sm2Engine.processBlock(finalByte, 0, finalByte.length); byte[] bytes = sm2Engine.processBlock(finalByte, 0, finalByte.length);
return new String(bytes, StandardCharsets.UTF_8); return new String(bytes, StandardCharsets.UTF_8);
@ -100,10 +94,10 @@ public class Sm2Utils {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider()); KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
kpg.initialize(sm2Spec, new SecureRandom()); kpg.initialize(sm2Spec, new SecureRandom());
KeyPair keyPair = kpg.generateKeyPair(); KeyPair keyPair = kpg.generateKeyPair();
BCECPublicKey bcecPublicKey = (BCECPublicKey)keyPair.getPublic(); BCECPublicKey bcecPublicKey = (BCECPublicKey) keyPair.getPublic();
String publicKey = Hex.toHexString(bcecPublicKey.getQ().getEncoded(false)); String publicKey = Hex.toHexString(bcecPublicKey.getQ().getEncoded(false));
System.out.println("公钥:\n" + publicKey); System.out.println("公钥:\n" + publicKey);
BCECPrivateKey bcecPrivateKey = (BCECPrivateKey)keyPair.getPrivate(); BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) keyPair.getPrivate();
String privateKey = bcecPrivateKey.getD().toString(16); String privateKey = bcecPrivateKey.getD().toString(16);
System.out.println("私钥:\n" + privateKey); System.out.println("私钥:\n" + privateKey);
} }
@ -111,13 +105,13 @@ public class Sm2Utils {
private static BCECPublicKey getPublicKey(String publicKey) throws Exception { private static BCECPublicKey getPublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("EC"); KeyFactory keyFactory = KeyFactory.getInstance("EC");
ECPoint ecPoint = param.getCurve().decodePoint(Hex.decode(publicKey)); ECPoint ecPoint = param.getCurve().decodePoint(Hex.decode(publicKey));
return (BCECPublicKey)keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, param)); return (BCECPublicKey) keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, param));
} }
private static BCECPrivateKey getPrivateKey(String privateKey) throws Exception { private static BCECPrivateKey getPrivateKey(String privateKey) throws Exception {
BigInteger d = new BigInteger(privateKey, 16); BigInteger d = new BigInteger(privateKey, 16);
KeyFactory keyFactory = KeyFactory.getInstance("EC"); KeyFactory keyFactory = KeyFactory.getInstance("EC");
return (BCECPrivateKey)keyFactory.generatePrivate(new ECPrivateKeySpec(d, param)); return (BCECPrivateKey) keyFactory.generatePrivate(new ECPrivateKeySpec(d, param));
} }
private static byte[] addBitIfNeed(byte[] base64Decode) { private static byte[] addBitIfNeed(byte[] base64Decode) {

View File

@ -0,0 +1,22 @@
package cn.zyjblogs.starter.common.utils.crypto.sm;
public class GmSmException extends RuntimeException {
public GmSmException() {
}
public GmSmException(String message) {
super(message);
}
public GmSmException(String message, Throwable cause) {
super(message, cause);
}
public GmSmException(Throwable cause) {
super(cause);
}
public GmSmException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,389 @@
package cn.zyjblogs.starter.common.utils.crypto.sm.sm2;
import cn.zyjblogs.starter.common.utils.crypto.Sm2Utils;
import cn.zyjblogs.starter.common.utils.crypto.sm.GmSmException;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
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.encoders.Hex;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Locale;
/**
* 国密SM2非对称加密算法
*/
public class SM2 {
public static final String CRYPTO_NAME_SM2 = "sm2p256v1";
private static byte SM2_CIPHER_FIRST_BIT = 4;
public enum EncodeType {
UTF8,
HEX,
BASE64
}
/**
* 生成SM2公私钥对
* <p>
* BC库使用的公钥=64个字节+1个字节04标志位BC库使用的私钥=32个字节
* SM2秘钥的组成部分有 私钥D,公钥X,公钥Y, 他们都可以用长度为64的16进制的HEX串表示
* SM2公钥并不是直接由X+Y表示, 而是额外添加了一个头,当启用压缩时:公钥=有头+公钥X,即省略了公钥Y的部分
*
* @param compressed 是否压缩公钥加密解密都使用BC库才能使用压缩
* @return SM2 HEX字符串格式秘钥对
*/
public static SM2KeyPair generateSm2Keys(boolean compressed) {
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2);
// 构造domain参数
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
// 创建秘钥对生成器
ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
// 初始化生成器,带上随机数
keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, new SecureRandom()));
// 生成秘钥对
AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
// 把公钥转换为椭圆点
ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic();
ECPoint ecPoint = publicKeyParameters.getQ();
// 把公钥转换为HEX
// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04,默认压缩公钥
String publicKey = Hex.toHexString(ecPoint.getEncoded(compressed)).toUpperCase(Locale.ROOT);
// 把私钥转换为HEX
ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
BigInteger intPrivateKey = privateKeyParameters.getD();
String privateKey = intPrivateKey.toString(16).toUpperCase(Locale.ROOT);
// 构造HEX秘钥对并返回
return new SM2KeyPair(publicKey, privateKey);
}
/**
* SM2加密算法
*
* @param pubKey 公钥
* @param data 待加密的数据
* @return 密文BC库产生的密文带由04标识符与非BC库对接时需要去掉开头的04
*/
public static String encrypt(String pubKey, String data) {
// 按国密排序标准加密
return encrypt(pubKey, data, SM2EngineExtend.CIPHER_MODE_NORM, EncodeType.UTF8, EncodeType.HEX);
}
/**
* SM2加密算法
*
* @param pubKey 公钥
* @param data 待加密的数据
* @param cipherMode 密文排列方式0-C1C2C31-C1C3C2
* @return 密文BC库产生的密文带由04标识符与非BC库对接时需要去掉开头的04
*/
public static String encrypt(String pubKey, String data, int cipherMode, EncodeType inputType, EncodeType outType) {
try {
// 非压缩模式公钥对接放是128位HEX秘钥需要为BC库加上04标记
if (pubKey.length() == 128) {
pubKey = "04" + pubKey;
}
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2);
// 构造ECC算法参数曲线方程椭圆曲线G点大整数N
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
//提取公钥点
ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(pubKey));
// 公钥前面的02或者03表示是压缩公钥04表示未压缩公钥, 04的时候可以去掉前面的04
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
SM2EngineExtend sm2Engine = new SM2EngineExtend();
// 设置sm2为加密模式
sm2Engine.init(true, cipherMode, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
byte[] in;
if (EncodeType.HEX.equals(inputType)) {
in = Hex.decode(data);
} else if (EncodeType.BASE64.equals(inputType)) {
in = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8));
} else {
in = data.getBytes(StandardCharsets.UTF_8);
}
byte[] arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
if (EncodeType.BASE64.equals(outType)) {
byte[] base64Bytes = Base64.getEncoder().encode(arrayOfBytes);
return new String(base64Bytes, StandardCharsets.UTF_8);
} else if (EncodeType.HEX.equals(outType)) {
return Hex.toHexString(arrayOfBytes).toUpperCase(Locale.ROOT);
} else {
return new String(arrayOfBytes, StandardCharsets.UTF_8);
}
} catch (Exception e) {
throw new GmSmException(e);
}
}
private static byte[] addBitIfNeed(byte[] base64Decode) {
byte first = base64Decode[0];
if (first == SM2_CIPHER_FIRST_BIT) {
return base64Decode;
} else {
byte[] finalByte = new byte[base64Decode.length + 1];
finalByte[0] = SM2_CIPHER_FIRST_BIT;
System.arraycopy(base64Decode, 0, finalByte, 1, base64Decode.length);
return finalByte;
}
}
/**
* SM2解密算法
*
* @param priKey 私钥
* @param cipherData 密文数据
* @return 解密后的数据
*/
public static String decrypt(String priKey, String cipherData) {
// // 按国密排序标准解密
return decrypt(priKey, cipherData, SM2EngineExtend.CIPHER_MODE_NORM, EncodeType.HEX, EncodeType.UTF8);
}
/**
* SM2解密算法
*
* @param priKey 私钥
* @param cipherData 密文数据
* @param cipherMode 密文排列方式 0-C1C2C31-C1C3C2
* @return 解密后的数据
*/
public static String decrypt(String priKey, String cipherData, int cipherMode, EncodeType inputType, EncodeType outType) {
try {
byte[] cipherDataByte;
if (EncodeType.HEX.equals(inputType)) {
// 使用BC库加解密时密文以04开头传入的密文前面没有04则补上
if (!cipherData.startsWith("04")) {
cipherData = "04" + cipherData;
}
cipherDataByte = Hex.decode(cipherData);
} else if (EncodeType.BASE64.equals(inputType)) {
cipherDataByte = Base64.getDecoder().decode(cipherData);
cipherDataByte = addBitIfNeed(cipherDataByte);
} else {
cipherDataByte = cipherData.getBytes(StandardCharsets.UTF_8);
}
//获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2);
//构造domain参数
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
BigInteger privateKeyD = new BigInteger(priKey, 16);
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
SM2EngineExtend sm2Engine = new SM2EngineExtend();
// 设置sm2为解密模式
sm2Engine.init(false, cipherMode, privateKeyParameters);
byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
if (EncodeType.HEX.equals(outType)) {
return Hex.toHexString(arrayOfBytes).toUpperCase(Locale.ROOT);
} else if (EncodeType.BASE64.equals(outType)) {
byte[] base64Bytes = Base64.getEncoder().encode(arrayOfBytes);
return new String(base64Bytes, StandardCharsets.UTF_8);
} else {
return new String(arrayOfBytes, StandardCharsets.UTF_8);
}
} catch (Exception e) {
throw new GmSmException(e);
}
}
/**
* 签名
*
* @param priKey 私钥
* @param plainText 待签名文本
* @return 签名
*/
public static String sign(String priKey, String plainText) {
try {
// 构造提供器
BouncyCastleProvider provider = new BouncyCastleProvider();
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2);
// 构造椭圆参数规格
ECParameterSpec ecParameterSpec = new ECParameterSpec(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN(), sm2ECParameters.getH());
// 创建Key工厂
KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
// 创建签名对象
Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);
// 将私钥HEX字符串转换为X值
BigInteger bigInteger = new BigInteger(priKey, 16);
// 生成SM2私钥
BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) keyFactory.generatePrivate(new ECPrivateKeySpec(bigInteger,
ecParameterSpec));
// 初始化为签名状态
signature.initSign(bcecPrivateKey);
// 传入签名字节
signature.update(plainText.getBytes());
// 签名
return Hex.toHexString(signature.sign()).toUpperCase(Locale.ROOT);
} catch (Exception e) {
throw new GmSmException(e);
}
}
/**
* 验签
*
* @param pubKey 公钥
* @param plainText 明文
* @param signatureValue 签名
* @return 验签结果
*/
public static boolean verify(String pubKey, String plainText, String signatureValue) {
// 非压缩模式公钥对接放是128位HEX秘钥需要为BC库加上04标记
if (pubKey.length() == 128) {
pubKey = "04" + pubKey;
}
try {
// 构造提供器
BouncyCastleProvider provider = new BouncyCastleProvider();
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2);
// 构造椭圆参数规格
ECParameterSpec ecParameterSpec = new ECParameterSpec(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN(), sm2ECParameters.getH());
// 创建Key工厂
KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
// 创建签名对象
Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);
// 将公钥HEX字符串转换为椭圆曲线对应的点
ECPoint ecPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(pubKey));
BCECPublicKey bcecPublicKey = (BCECPublicKey) keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, ecParameterSpec));
// 初始化为验签状态
signature.initVerify(bcecPublicKey);
signature.update(plainText.getBytes());
return signature.verify(Hex.decode(signatureValue));
} catch (Exception e) {
throw new GmSmException(e);
}
}
/**
* 证书验签
*
* @param certStr 证书串
* @param plaintext 签名原文
* @param signValueStr 签名产生签名值 此处的签名值实际上就是 R和S的sequence
* @return 证书验证结果
*/
public static boolean certVerify(String certStr, String plaintext, String signValueStr) {
try {
// 构造提供器
BouncyCastleProvider provider = new BouncyCastleProvider();
// 解析证书
byte[] signValue = Hex.decode(signValueStr);
CertificateFactory factory = new CertificateFactory();
X509Certificate certificate = (X509Certificate) factory
.engineGenerateCertificate(new ByteArrayInputStream(Hex.decode(certStr)));
// 验证签名
Signature signature = Signature.getInstance(certificate.getSigAlgName(), provider);
signature.initVerify(certificate);
signature.update(plaintext.getBytes());
return signature.verify(signValue);
} catch (Exception e) {
throw new GmSmException(e);
}
}
public static void main(String[] args) {
String data = "dPhq2XdoMcgD5m7M0I51SX7MkzMerWMcPdBdv/tX8B5jOyM28n+CcXUn721/9N0ELVgC2P0eBRn4jD04rPScJd5izcC7+xXT5LUwbV2S6wc0g2RC8nkuZITc4rdrACPvNxd18b6y";
String pub = "0417f347d7fa08ae6ad9bf8ef6ac6c313810e05044290f7c18dc9b913b252603505cf7cdbf7ac7d88de508e78bbc2d74cb28c0a90724ed4b751cc69bdfe55b68de";
String pri = "73d76cf4f553535d6ec45478fb1581baa0c83e166b347af10ab129966d3f187f";
String key = "0123456789abcdeffedcba9876543210";
try {
String decrypt = Sm2Utils.decrypt(pri, data, SM2Engine.Mode.C1C3C2);
System.out.println(decrypt);
String encrypt = Sm2Utils.encrypt(pub, decrypt, SM2Engine.Mode.C1C3C2);
System.out.println(encrypt);
String decrypt1 = Sm2Utils.decrypt(pri, encrypt, SM2Engine.Mode.C1C3C2);
System.out.println(decrypt1);
System.out.println(Sm2Utils.decrypt(pri, "BCWhJJ0BFPt/RuhS37sk22/5GuemkzG7kt+CLwRSz34taiKPjc0TDoY959dCf7C2cZJ2uzLoqRmcH/pV7uWGhPzTIZmKM8wPpVIeuN616dNVm+5/YpaQfcawis6KpJOeeU4fcyrYf9wcawtkow=="));
System.out.println("MDRiZGVjZDU2ODJmNTk5NjJiOTUyOTYwOTQ1MjVjMzcwN2U5MDUxZDJiMDczN2E3MDUxYzNjNDNmMWQyOTczNzBiMTM5YzU3YTA1OGEzMTU3NjM1NGY1NGZjNWE5N2U4Mzg3ZjE1NGM0ZWNhNjIyNDkzMGNlMGIzN2M2ZDI3NjhkMDViMjc4ODY0ODBiN2ZjYzA3YmNkMzM2OWJmNjQ2MGJlN2U0MzQyNTU2NGVkMDQyNzViZDRmZWFmYTUzYzRmOTIwY2Q0OTAxN2ZhMDA=".length());
System.out.println("04bdecd5682f59962b95296094525c3707e9051d2b0737a7051c3c43f1d297370b139c57a058a31576354f54fc5a97e8387f154c4eca6224930ce0b37c6d2768d05b27886480b7fcc07bcd3369bf6460be7e43425564ed04275bd4feafa53c4f920cd49017fa00".length());
String decrypt2 = SM2.decrypt(pri, "BCWhJJ0BFPt/RuhS37sk22/5GuemkzG7kt+CLwRSz34taiKPjc0TDoY959dCf7C2cZJ2uzLoqRmcH/pV7uWGhPzTIZmKM8wPpVIeuN616dNVm+5/YpaQfcawis6KpJOeeU4fcyrYf9wcawtkow==", 1, SM2.EncodeType.BASE64, SM2.EncodeType.UTF8);
System.out.println("-------------");
System.out.println(decrypt2);
String encrypt1 = SM2.encrypt(pub, decrypt2);
System.out.println("-------------");
System.out.println(encrypt1);
String decrypt3 = SM2.decrypt(pri, encrypt1);
System.out.println("aaa:" + decrypt3);
String decrypt4 = SM2.encrypt(pub, decrypt3, 1, SM2.EncodeType.UTF8, SM2.EncodeType.BASE64);
System.out.println(decrypt4);
System.out.println("-----------");
String decrypt5 = SM2.decrypt(pri, decrypt4, 1, SM2.EncodeType.BASE64, SM2.EncodeType.BASE64);
System.out.println(decrypt5);
String decrypt5_1 = SM2.encrypt(pub, decrypt5, 1, SM2.EncodeType.BASE64, SM2.EncodeType.BASE64);
System.out.println(decrypt5_1);
String decrypt5_2 = SM2.decrypt(pri, decrypt5_1, 1, SM2.EncodeType.BASE64, SM2.EncodeType.BASE64);
System.out.println(decrypt5_2);
System.out.println(new String(Base64.getDecoder().decode(decrypt5)));
String decrypt6 = SM2.encrypt(pub, decrypt5, 1, SM2.EncodeType.BASE64, SM2.EncodeType.HEX);
System.out.println(decrypt6);
System.out.println("-----------");
String decrypt7 = SM2.decrypt(pri, decrypt6, 1, SM2.EncodeType.HEX, SM2.EncodeType.UTF8);
System.out.println(decrypt7);
String decrypt8 = SM2.decrypt(pri, data, 1, EncodeType.BASE64, SM2.EncodeType.UTF8);
System.out.println(decrypt8);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,293 @@
package cn.zyjblogs.starter.common.utils.crypto.sm.sm2;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.math.ec.ECConstants;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import java.math.BigInteger;
import java.security.SecureRandom;
public class SM2EngineExtend {
private final Digest digest;
/**
* 是否为加密模式
*/
private boolean forEncryption;
private ECKeyParameters ecKey;
private ECDomainParameters ecParams;
private int curveLength;
private SecureRandom random;
/**
* 密文排序方式
*/
private int cipherMode;
/**
* BC库默认排序方式-C1C2C3
*/
public static int CIPHER_MODE_BC = 0;
/**
* 国密标准排序方式-C1C3C2
*/
public static int CIPHER_MODE_NORM = 1;
public SM2EngineExtend() {
this(new SM3Digest());
}
public SM2EngineExtend(Digest digest) {
this.digest = digest;
}
/**
* 设置密文排序方式
*
* @param cipherMode 排序方式
*/
public void setCipherMode(int cipherMode) {
this.cipherMode = cipherMode;
}
/**
* 默认初始化方法使用国密排序标准
*
* @param forEncryption - 是否以加密模式初始化
* @param param - 曲线参数
*/
public void init(boolean forEncryption, CipherParameters param) {
init(forEncryption, CIPHER_MODE_NORM, param);
}
/**
* 默认初始化方法使用国密排序标准
*
* @param forEncryption 是否以加密模式初始化
* @param cipherMode 加密数据排列模式1-标准排序0-BC默认排序
* @param param 曲线参数
*/
public void init(boolean forEncryption, int cipherMode, CipherParameters param) {
this.forEncryption = forEncryption;
this.cipherMode = cipherMode;
if (forEncryption) {
ParametersWithRandom rParam = (ParametersWithRandom) param;
ecKey = (ECKeyParameters) rParam.getParameters();
ecParams = ecKey.getParameters();
ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
if (s.isInfinity()) {
throw new IllegalArgumentException("invalid key: [h]Q at infinity");
}
random = rParam.getRandom();
} else {
ecKey = (ECKeyParameters) param;
ecParams = ecKey.getParameters();
}
curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;
}
/**
* 加密或解密输入数据
*
* @param in 输入数据字节
* @param inOff 偏移
* @param inLen 长度
* @return 解析后字节
* @throws InvalidCipherTextException 异常
*/
public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
if (forEncryption) {
// 加密
return encrypt(in, inOff, inLen);
} else {
return decrypt(in, inOff, inLen);
}
}
/**
* 加密实现根据cipherMode输出指定排列的结果默认按标准方式排列
*
* @param in 输入数据字节
* @param inOff 偏移
* @param inLen 长度
* @return 解析后字节
*/
private byte[] encrypt(byte[] in, int inOff, int inLen) {
byte[] c2 = new byte[inLen];
System.arraycopy(in, inOff, c2, 0, c2.length);
byte[] c1;
ECPoint kPB;
do {
BigInteger k = nextK();
ECPoint c1P = ecParams.getG().multiply(k).normalize();
c1 = c1P.getEncoded(false);
kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();
kdf(digest, kPB, c2);
}
while (notEncrypted(c2, in, inOff));
byte[] c3 = new byte[digest.getDigestSize()];
addFieldElement(digest, kPB.getAffineXCoord());
digest.update(in, inOff, inLen);
addFieldElement(digest, kPB.getAffineYCoord());
digest.doFinal(c3, 0);
if (cipherMode == CIPHER_MODE_NORM) {
return Arrays.concatenate(c1, c3, c2);
}
return Arrays.concatenate(c1, c2, c3);
}
/**
* 解密实现默认按标准排列方式解密解密时解出c2部分原文并校验c3部分
*
* @param in 输入数据字节
* @param inOff 偏移
* @param inLen 长度
* @return 解析后字节
* @throws InvalidCipherTextException
*/
private byte[] decrypt(byte[] in, int inOff, int inLen)
throws InvalidCipherTextException {
byte[] c1 = new byte[curveLength * 2 + 1];
System.arraycopy(in, inOff, c1, 0, c1.length);
ECPoint c1P = ecParams.getCurve().decodePoint(c1);
ECPoint s = c1P.multiply(ecParams.getH());
if (s.isInfinity()) {
throw new InvalidCipherTextException("[h]C1 at infinity");
}
c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();
byte[] c2 = new byte[inLen - c1.length - digest.getDigestSize()];
if (cipherMode == CIPHER_MODE_BC) {
System.arraycopy(in, inOff + c1.length, c2, 0, c2.length);
} else {
// C1 C3 C2
System.arraycopy(in, inOff + c1.length + digest.getDigestSize(), c2, 0, c2.length);
}
kdf(digest, c1P, c2);
byte[] c3 = new byte[digest.getDigestSize()];
addFieldElement(digest, c1P.getAffineXCoord());
digest.update(c2, 0, c2.length);
addFieldElement(digest, c1P.getAffineYCoord());
digest.doFinal(c3, 0);
int check = 0;
// 检查密文输入值C3部分和由摘要生成的C3是否一致
if (cipherMode == CIPHER_MODE_BC) {
for (int i = 0; i != c3.length; i++) {
check |= c3[i] ^ in[c1.length + c2.length + i];
}
} else {
for (int i = 0; i != c3.length; i++) {
check |= c3[i] ^ in[c1.length + i];
}
}
clearBlock(c1);
clearBlock(c3);
if (check != 0) {
clearBlock(c2);
throw new InvalidCipherTextException("invalid cipher text");
}
return c2;
}
private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
for (int i = 0; i != encData.length; i++) {
if (encData[i] != in[inOff]) {
return false;
}
}
return true;
}
private void kdf(Digest digest, ECPoint c1, byte[] encData) {
int ct = 1;
int v = digest.getDigestSize();
byte[] buf = new byte[digest.getDigestSize()];
int off = 0;
for (int i = 1; i <= ((encData.length + v - 1) / v); i++) {
addFieldElement(digest, c1.getAffineXCoord());
addFieldElement(digest, c1.getAffineYCoord());
digest.update((byte) (ct >> 24));
digest.update((byte) (ct >> 16));
digest.update((byte) (ct >> 8));
digest.update((byte) ct);
digest.doFinal(buf, 0);
if (off + buf.length < encData.length) {
xor(encData, buf, off, buf.length);
} else {
xor(encData, buf, off, encData.length - off);
}
off += buf.length;
ct++;
}
}
private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
for (int i = 0; i != dRemaining; i++) {
data[dOff + i] ^= kdfOut[i];
}
}
private BigInteger nextK() {
int qBitLength = ecParams.getN().bitLength();
BigInteger k;
do {
k = new BigInteger(qBitLength, random);
}
while (k.equals(ECConstants.ZERO) || k.compareTo(ecParams.getN()) >= 0);
return k;
}
private void addFieldElement(Digest digest, ECFieldElement v) {
byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());
digest.update(p, 0, p.length);
}
/**
* clear possible sensitive data
*/
private void clearBlock(
byte[] block) {
Arrays.fill(block, (byte) 0);
}
}

View File

@ -0,0 +1,36 @@
package cn.zyjblogs.starter.common.utils.crypto.sm.sm2;
public class SM2KeyPair {
/**
* 公钥
*/
private String publicKey;
/**
* 私钥
*/
private String privateKey;
public SM2KeyPair(String publicKey, String privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public SM2KeyPair() {
}
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
}

View File

@ -0,0 +1,36 @@
package cn.zyjblogs.starter.common.utils.crypto.sm.sm3;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
/**
* 国密SM3摘要算法
*/
public class SM3 {
public static String digest(String input) {
// 创建摘要器
SM3Digest sm3Digest = new SM3Digest();
// 解析输入数据
byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
// 构造输出数据缓冲区
byte[] out = new byte[32];
// 设置待摘要字节数据
sm3Digest.update(bytes, 0, bytes.length);
// 执行摘要
sm3Digest.doFinal(out, 0);
// 返回HEX字符串
return Hex.toHexString(out).toUpperCase(Locale.ROOT);
}
}

View File

@ -0,0 +1,190 @@
package cn.zyjblogs.starter.common.utils.crypto.sm.sm4;
import cn.zyjblogs.starter.common.utils.crypto.sm.GmSmException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Locale;
/**
* 国密SM4对称加密算法
*/
public class SM4 {
// 算法
private static final String SM4_ALGORITHM = "SM4";
// 密钥长度128位
private static final int DEFAULT_KEY_SIZE = 128;
// 变换规则CBC模式
private static final String TRANSFORMATION_CBC = "SM4/CBC/PKCS5Padding";
// 变换规则ECB模式
private static final String TRANSFORMATION_ECB = "SM4/ECB/PKCS5Padding";
// 追加提BC提供器
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 生成默认Key
*
* @return key
*/
public static String generateKey() {
return generateKey(DEFAULT_KEY_SIZE);
}
/**
* 生成制定长度Key
*
* @param keySize key 长度
* @return key
*/
public static String generateKey(int keySize) {
try {
// 创建Key生成器
KeyGenerator kg = KeyGenerator.getInstance(SM4_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
// 初始化
kg.init(keySize, new SecureRandom());
// 生成Key
byte[] encoded = kg.generateKey().getEncoded();
// 返回HEX字符串
return Hex.toHexString(encoded).toUpperCase(Locale.ROOT);
} catch (Exception e) {
throw new GmSmException(e);
}
}
/**
* 加密CBC模式
*
* @param keyHex 秘钥HEX字符串
* @param planText 明文字符串
* @param ivHex 向量HEX字符串
* @return 加密后的HEX字符串
*/
public static String encrypt(String keyHex, String planText, String ivHex) {
try {
// 创建加密对象
Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC);
// 创建加密规则
SecretKeySpec keySpec = new SecretKeySpec(Hex.decode(keyHex), SM4_ALGORITHM);
// 创建IV向量
IvParameterSpec ivSpec = new IvParameterSpec(Hex.decode(ivHex));
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
// 调用加密方法
byte[] outputBytes = cipher.doFinal(planText.getBytes(StandardCharsets.UTF_8));
return Hex.toHexString(outputBytes).toUpperCase(Locale.ROOT);
} catch (Exception e) {
throw new GmSmException(e);
}
}
/**
* 解密CBC模式
*
* @param keyHex 秘钥HEX字符串
* @param cipherDataHex 密文的HEX字符串
* @param ivHex 向量HEX字符串
* @return 解密后的明文
*/
public static String decrypt(String keyHex, String cipherDataHex, String ivHex) {
try {
// 创建加密对象
Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC);
// 创建加密规则
SecretKeySpec keySpec = new SecretKeySpec(Hex.decode(keyHex), SM4_ALGORITHM);
// 创建IV向量
IvParameterSpec ivSpec = new IvParameterSpec(Hex.decode(ivHex));
// 初始化
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// 调用加密方法
byte[] outputBytes = cipher.doFinal(Hex.decode(cipherDataHex));
return new String(outputBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new GmSmException(e);
}
}
/**
* 加密ECB模式
*
* @param keyHex 秘钥HEX字符串
* @param planText 明文字符串
* @return 加密后的HEX字符串
*/
public static String encrypt(String keyHex, String planText) {
try {
// 创建加密对象
Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB);
// 创建加密规则
SecretKeySpec keySpec = new SecretKeySpec(Hex.decode(keyHex), SM4_ALGORITHM);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// 调用加密方法
byte[] outputBytes = cipher.doFinal(planText.getBytes(StandardCharsets.UTF_8));
return Hex.toHexString(outputBytes).toUpperCase(Locale.ROOT);
} catch (Exception e) {
throw new GmSmException(e);
}
}
/**
* 解密ECB模式
*
* @param keyHex 秘钥HEX字符串
* @param cipherDataHex 密文的HEX字符串
* @return 解密后的明文
*/
public static String decrypt(String keyHex, String cipherDataHex) {
try {
// 创建加密对象
Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB);
// 创建加密规则
SecretKeySpec keySpec = new SecretKeySpec(Hex.decode(keyHex), SM4_ALGORITHM);
// 初始化
cipher.init(Cipher.DECRYPT_MODE, keySpec);
// 调用加密方法
byte[] outputBytes = cipher.doFinal(Hex.decode(cipherDataHex));
return new String(outputBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new GmSmException(e);
}
}
}