Browse Source

support x,y for sm2

Looly 5 years ago
parent
commit
f6585adec4

+ 1 - 0
CHANGELOG.md

@@ -22,6 +22,7 @@
 * 【crypto】     增加PemUtil工具类
 * 【dfa   】     WordTree增加Filter,支持自定义特殊字符过滤器
 * 【poi   】     对于POI依赖升级到4.1.2
+* 【crypto】     增加国密SM2验签密钥格式支持(issue#686@Github)
 
 ### Bug修复
 

+ 15 - 0
hutool-core/src/main/java/cn/hutool/core/util/HexUtil.java

@@ -1,6 +1,7 @@
 package cn.hutool.core.util;
 
 import java.awt.Color;
+import java.math.BigInteger;
 import java.nio.charset.Charset;
 
 /**
@@ -198,6 +199,7 @@ public class HexUtil {
 		if (StrUtil.isEmpty(hexStr)) {
 			return null;
 		}
+		hexStr = StrUtil.removeAll(hexStr, ' ');
 		return decodeHex(hexStr.toCharArray());
 	}
 
@@ -338,6 +340,19 @@ public class HexUtil {
 		builder.append(toDigits[low]);
 	}
 
+	/**
+	 * Hex(16进制)字符串转为BigInteger
+	 * @param hexStr Hex(16进制字符串)
+	 * @return {@link BigInteger}
+	 * @since 5.2.0
+	 */
+	public static BigInteger toBigInteger(String hexStr){
+		if(null == hexStr){
+			return null;
+		}
+		return new BigInteger(hexStr, 16);
+	}
+
 	// ---------------------------------------------------------------------------------------- Private method start
 
 	/**

+ 7 - 0
hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java

@@ -38,4 +38,11 @@ public class HexUtilTest {
 		boolean isHex = HexUtil.isHexNumber(a);
 		Assert.assertTrue(isHex);
 	}
+
+	@Test
+	public void decodeTest(){
+		String str = "e8c670380cb220095268f40221fc748fa6ac39d6e930e63c30da68bad97f885d";
+		Assert.assertArrayEquals(HexUtil.decodeHex(str),
+				HexUtil.decodeHex(str.toUpperCase()));
+	}
 }

+ 197 - 70
hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java

@@ -1,29 +1,27 @@
 package cn.hutool.crypto;
 
 import cn.hutool.core.util.HexUtil;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.ECDomainParameters;
-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.jce.ECNamedCurveTable;
-import org.bouncycastle.jce.ECPointUtil;
-import org.bouncycastle.jce.interfaces.ECKey;
-import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
 import org.bouncycastle.jce.spec.ECNamedCurveSpec;
 import org.bouncycastle.jce.spec.ECParameterSpec;
 import org.bouncycastle.math.ec.ECCurve;
 
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
+import java.security.InvalidKeyException;
+import java.security.Key;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.spec.ECFieldFp;
 import java.security.spec.ECPoint;
 import java.security.spec.ECPublicKeySpec;
-import java.security.spec.EllipticCurve;
 
 /**
  * Bouncy Castle相关工具类封装
@@ -70,68 +68,99 @@ public class BCUtil {
 	}
 
 	/**
-	 * 解码恢复EC压缩公钥,支持Base64和Hex编码,(基于BouncyCastle)<br>
-	 * 见:https://www.cnblogs.com/xinzhao/p/8963724.html
+	 * 解码恢复EC压缩公钥,支持Base64和Hex编码,(基于BouncyCastle)
 	 *
 	 * @param encodeByte 压缩公钥
-	 * @param curveName  EC曲线名,例如{@link KeyUtil#SM2_DEFAULT_CURVE}
+	 * @param curveName  EC曲线名,例如{@link SmUtil#SM2_DOMAIN_PARAMS}
 	 * @return 公钥
 	 * @since 4.4.4
 	 */
 	public static PublicKey decodeECPoint(byte[] encodeByte, String curveName) {
-		final ECNamedCurveParameterSpec namedSpec = ECNamedCurveTable.getParameterSpec(curveName);
-		final ECCurve curve = namedSpec.getCurve();
-		final EllipticCurve ecCurve = new EllipticCurve(//
-				new ECFieldFp(curve.getField().getCharacteristic()), //
-				curve.getA().toBigInteger(), //
-				curve.getB().toBigInteger());
-		// 根据X恢复点Y
-		final ECPoint point = ECPointUtil.decodePoint(ecCurve, encodeByte);
+		final X9ECParameters x9ECParameters = ECUtil.getNamedCurveByName(curveName);
+		final ECCurve curve = x9ECParameters.getCurve();
+		final ECPoint point = EC5Util.convertPoint(curve.decodePoint(encodeByte));
 
 		// 根据曲线恢复公钥格式
-		ECNamedCurveSpec ecSpec = new ECNamedCurveSpec(curveName, curve, namedSpec.getG(), namedSpec.getN());
+		final ECNamedCurveSpec ecSpec = new ECNamedCurveSpec(curveName, curve, x9ECParameters.getG(), x9ECParameters.getN());
 
-		final KeyFactory PubKeyGen = KeyUtil.getKeyFactory("EC");
 		try {
-			return PubKeyGen.generatePublic(new ECPublicKeySpec(point, ecSpec));
+			return KeyUtil.getKeyFactory("EC").generatePublic(new ECPublicKeySpec(point, ecSpec));
 		} catch (GeneralSecurityException e) {
 			throw new CryptoException(e);
 		}
 	}
 
 	/**
-	 * ECKey转换为ECKeyParameters
+	 * 构建ECDomainParameters对象
 	 *
-	 * @param ecKey BCECPrivateKey或者BCECPublicKey
-	 * @return ECPrivateKeyParameters或者ECPublicKeyParameters
-	 * @since 5.1.6
+	 * @param parameterSpec ECParameterSpec
+	 * @return {@link ECDomainParameters}
+	 * @since 5.2.0
+	 */
+	public static ECDomainParameters toDomainParams(ECParameterSpec parameterSpec) {
+		return new ECDomainParameters(
+				parameterSpec.getCurve(),
+				parameterSpec.getG(),
+				parameterSpec.getN(),
+				parameterSpec.getH());
+	}
+
+	/**
+	 * 构建ECDomainParameters对象
+	 *
+	 * @param curveName Curve名称
+	 * @return {@link ECDomainParameters}
+	 * @since 5.2.0
+	 */
+	public static ECDomainParameters toDomainParams(String curveName) {
+		return toDomainParams(ECUtil.getNamedCurveByName(curveName));
+	}
+
+	/**
+	 * 构建ECDomainParameters对象
+	 *
+	 * @param x9ECParameters {@link X9ECParameters}
+	 * @return {@link ECDomainParameters}
+	 * @since 5.2.0
 	 */
-	public static ECKeyParameters toParams(ECKey ecKey) {
-		final ECParameterSpec parameterSpec = ecKey.getParameters();
-		final ECDomainParameters ecDomainParameters = toDomainParameters(parameterSpec);
+	public static ECDomainParameters toDomainParams(X9ECParameters x9ECParameters) {
+		return new ECDomainParameters(
+				x9ECParameters.getCurve(),
+				x9ECParameters.getG(),
+				x9ECParameters.getN(),
+				x9ECParameters.getH()
+		);
+	}
 
-		if (ecKey instanceof BCECPrivateKey) {
-			return new ECPrivateKeyParameters(((BCECPrivateKey) ecKey).getD(), ecDomainParameters);
-		} else if (ecKey instanceof BCECPublicKey) {
-			return new ECPublicKeyParameters(((BCECPublicKey) ecKey).getQ(), ecDomainParameters);
+	/**
+	 * 密钥转换为AsymmetricKeyParameter
+	 *
+	 * @param key PrivateKey或者PublicKey
+	 * @return ECPrivateKeyParameters或者ECPublicKeyParameters
+	 * @since 5.2.0
+	 */
+	public static AsymmetricKeyParameter toParams(Key key) {
+		try {
+			if (key instanceof PrivateKey) {
+				return ECUtil.generatePrivateKeyParameter((PrivateKey) key);
+			} else if (key instanceof PublicKey) {
+				return ECUtil.generatePublicKeyParameter((PublicKey) key);
+			}
+		} catch (InvalidKeyException e) {
+			throw new CryptoException(e);
 		}
 
 		return null;
 	}
 
 	/**
-	 * 构建ECDomainParameters对象
+	 * 转换为 ECPrivateKeyParameters
 	 *
-	 * @param parameterSpec ECParameterSpec
-	 * @return ECDomainParameters
-	 * @since 5.1.6
+	 * @param dHex 私钥d值16进制字符串
+	 * @return ECPrivateKeyParameters
 	 */
-	public static ECDomainParameters toDomainParameters(ECParameterSpec parameterSpec) {
-		return new ECDomainParameters(
-				parameterSpec.getCurve(),
-				parameterSpec.getG(),
-				parameterSpec.getN(),
-				parameterSpec.getH());
+	public static ECPrivateKeyParameters toSm2Params(String dHex) {
+		return toSm2Params(HexUtil.toBigInteger(dHex));
 	}
 
 	/**
@@ -142,7 +171,38 @@ public class BCUtil {
 	 * @return ECPrivateKeyParameters
 	 */
 	public static ECPrivateKeyParameters toParams(String dHex, ECDomainParameters domainParameters) {
-		return new ECPrivateKeyParameters(new BigInteger(dHex, 16), domainParameters);
+		return toParams(new BigInteger(dHex, 16), domainParameters);
+	}
+
+	/**
+	 * 转换为 ECPrivateKeyParameters
+	 *
+	 * @param d 私钥d值
+	 * @return ECPrivateKeyParameters
+	 */
+	public static ECPrivateKeyParameters toSm2Params(byte[] d) {
+		return toSm2Params(new BigInteger(d));
+	}
+
+	/**
+	 * 转换为 ECPrivateKeyParameters
+	 *
+	 * @param d                私钥d值
+	 * @param domainParameters ECDomainParameters
+	 * @return ECPrivateKeyParameters
+	 */
+	public static ECPrivateKeyParameters toParams(byte[] d, ECDomainParameters domainParameters) {
+		return toParams(new BigInteger(d), domainParameters);
+	}
+
+	/**
+	 * 转换为 ECPrivateKeyParameters
+	 *
+	 * @param d 私钥d值
+	 * @return ECPrivateKeyParameters
+	 */
+	public static ECPrivateKeyParameters toSm2Params(BigInteger d) {
+		return toParams(d, SmUtil.SM2_DOMAIN_PARAMS);
 	}
 
 	/**
@@ -153,6 +213,9 @@ public class BCUtil {
 	 * @return ECPrivateKeyParameters
 	 */
 	public static ECPrivateKeyParameters toParams(BigInteger d, ECDomainParameters domainParameters) {
+		if(null == d){
+			return null;
+		}
 		return new ECPrivateKeyParameters(d, domainParameters);
 	}
 
@@ -161,12 +224,25 @@ public class BCUtil {
 	 *
 	 * @param x                公钥X
 	 * @param y                公钥Y
-	 * @param curve            ECCurve
 	 * @param domainParameters ECDomainParameters
 	 * @return ECPublicKeyParameters
 	 */
-	public static ECPublicKeyParameters toParams(BigInteger x, BigInteger y, ECCurve curve, ECDomainParameters domainParameters) {
-		return toParams(x.toByteArray(), y.toByteArray(), curve, domainParameters);
+	public static ECPublicKeyParameters toParams(BigInteger x, BigInteger y, ECDomainParameters domainParameters) {
+		if(null == x || null == y){
+			return null;
+		}
+		return toParams(x.toByteArray(), y.toByteArray(), domainParameters);
+	}
+
+	/**
+	 * 转换为SM2的ECPublicKeyParameters
+	 *
+	 * @param xHex 公钥X
+	 * @param yHex 公钥Y
+	 * @return ECPublicKeyParameters
+	 */
+	public static ECPublicKeyParameters toSm2Params(String xHex, String yHex) {
+		return toParams(xHex, yHex, SmUtil.SM2_DOMAIN_PARAMS);
 	}
 
 	/**
@@ -174,13 +250,22 @@ public class BCUtil {
 	 *
 	 * @param xHex             公钥X
 	 * @param yHex             公钥Y
-	 * @param curve            ECCurve
 	 * @param domainParameters ECDomainParameters
 	 * @return ECPublicKeyParameters
 	 */
-	public static ECPublicKeyParameters toParams(String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
-		return toParams(HexUtil.decodeHex(xHex), HexUtil.decodeHex(yHex),
-				curve, domainParameters);
+	public static ECPublicKeyParameters toParams(String xHex, String yHex, ECDomainParameters domainParameters) {
+		return toParams(HexUtil.decodeHex(xHex), HexUtil.decodeHex(yHex), domainParameters);
+	}
+
+	/**
+	 * 转换为SM2的ECPublicKeyParameters
+	 *
+	 * @param xBytes 公钥X
+	 * @param yBytes 公钥Y
+	 * @return ECPublicKeyParameters
+	 */
+	public static ECPublicKeyParameters toSm2Params(byte[] xBytes, byte[] yBytes) {
+		return toParams(xBytes, yBytes, SmUtil.SM2_DOMAIN_PARAMS);
 	}
 
 	/**
@@ -188,47 +273,89 @@ public class BCUtil {
 	 *
 	 * @param xBytes           公钥X
 	 * @param yBytes           公钥Y
-	 * @param curve            ECCurve
 	 * @param domainParameters ECDomainParameters
 	 * @return ECPublicKeyParameters
 	 */
-	public static ECPublicKeyParameters toParams(byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) {
-		final byte uncompressedFlag = 0x04;
-		int curveLength = getCurveLength(domainParameters);
-		xBytes = fixLength(curveLength, xBytes);
-		yBytes = fixLength(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);
+	public static ECPublicKeyParameters toParams(byte[] xBytes, byte[] yBytes, ECDomainParameters domainParameters) {
+		if(null == xBytes || null == yBytes){
+			return null;
+		}
+		final ECCurve curve = domainParameters.getCurve();
+		final int curveLength = getCurveLength(curve);
+		final byte[] encodedPubKey = encodePoint(xBytes, yBytes, curveLength);
 		return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
 	}
 
 	/**
-	 * 获取Curve长度
+	 * 公钥转换为 {@link ECPublicKeyParameters}
 	 *
-	 * @param ecKey Curve
-	 * @return Curve长度
+	 * @param publicKey 公钥,传入null返回null
+	 * @return {@link ECPublicKeyParameters}或null
+	 */
+	public static ECPublicKeyParameters toParams(PublicKey publicKey) {
+		if (null == publicKey) {
+			return null;
+		}
+		try {
+			return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
+		} catch (InvalidKeyException e) {
+			throw new CryptoException(e);
+		}
+	}
+
+	/**
+	 * 私钥转换为 {@link ECPrivateKeyParameters}
+	 *
+	 * @param privateKey 私钥,传入null返回null
+	 * @return {@link ECPrivateKeyParameters}或null
 	 */
-	public static int getCurveLength(ECKeyParameters ecKey) {
-		return getCurveLength(ecKey.getParameters());
+	public static ECPrivateKeyParameters toParams(PrivateKey privateKey) {
+		if (null == privateKey) {
+			return null;
+		}
+		try {
+			return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
+		} catch (InvalidKeyException e) {
+			throw new CryptoException(e);
+		}
+	}
+
+	/**
+	 * 将X,Y曲线点编码为bytes
+	 *
+	 * @param xBytes      X坐标bytes
+	 * @param yBytes      Y坐标bytes
+	 * @param curveLength 曲线编码后的长度
+	 * @return 编码bytes
+	 */
+	private static byte[] encodePoint(byte[] xBytes, byte[] yBytes, int curveLength) {
+		xBytes = fixLength(curveLength, xBytes);
+		yBytes = fixLength(curveLength, yBytes);
+		final byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
+
+		// 压缩类型:无压缩
+		encodedPubKey[0] = 0x04;
+		System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
+		System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
+
+		return encodedPubKey;
 	}
 
 	/**
 	 * 获取Curve长度
 	 *
-	 * @param domainParams ECDomainParameters
+	 * @param curve {@link ECCurve}
 	 * @return Curve长度
 	 */
-	public static int getCurveLength(ECDomainParameters domainParams) {
-		return (domainParams.getCurve().getFieldSize() + 7) / 8;
+	private static int getCurveLength(ECCurve curve) {
+		return (curve.getFieldSize() + 7) / 8;
 	}
 
 	/**
 	 * 修正长度
 	 *
 	 * @param curveLength 修正后的长度
-	 * @param src bytes
+	 * @param src         bytes
 	 * @return 修正后的bytes
 	 */
 	private static byte[] fixLength(int curveLength, byte[] src) {

+ 1 - 1
hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java

@@ -93,7 +93,7 @@ public class KeyUtil {
 	 * Default SM2 curve
 	 * </pre>
 	 */
-	public static final String SM2_DEFAULT_CURVE = "sm2p256v1";
+	public static final String SM2_DEFAULT_CURVE = SmUtil.SM2_CURVE_NAME;
 
 	/**
 	 * 生成 {@link SecretKey},仅用于对称加密和摘要算法密钥生成

+ 36 - 34
hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java

@@ -13,10 +13,9 @@ import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.gm.GMNamedCurves;
 import org.bouncycastle.crypto.digests.SM3Digest;
 import org.bouncycastle.crypto.params.ECDomainParameters;
-import org.bouncycastle.math.ec.ECPoint;
-import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 
@@ -28,30 +27,31 @@ import java.math.BigInteger;
 /**
  * SM国密算法工具类<br>
  * 此工具类依赖org.bouncycastle:bcpkix-jdk15on
- * 
+ *
  * @author looly
  * @since 4.3.2
  */
 public class SmUtil {
 
-	/*
+	/**
+	 * SM2默认曲线
+	 */
+	public static final String SM2_CURVE_NAME = "sm2p256v1";
+	/**
 	 * SM2推荐曲线参数(来自https://github.com/ZZMarquis/gmhelper)
 	 */
-	public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
-	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,
-			CURVE.getOrder(), CURVE.getCofactor());
+	public static final ECDomainParameters SM2_DOMAIN_PARAMS;
 
 	private final static int RS_LEN = 32;
 
+	static {
+		SM2_DOMAIN_PARAMS = BCUtil.toDomainParams(GMNamedCurves.getByName(SM2_CURVE_NAME));
+	}
+
 	/**
 	 * 创建SM2算法对象<br>
 	 * 生成新的私钥公钥对
-	 * 
+	 *
 	 * @return {@link SM2}
 	 */
 	public static SM2 sm2() {
@@ -62,9 +62,9 @@ public class SmUtil {
 	 * 创建SM2算法对象<br>
 	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
-	 * 
+	 *
 	 * @param privateKeyStr 私钥Hex或Base64表示
-	 * @param publicKeyStr 公钥Hex或Base64表示
+	 * @param publicKeyStr  公钥Hex或Base64表示
 	 * @return {@link SM2}
 	 */
 	public static SM2 sm2(String privateKeyStr, String publicKeyStr) {
@@ -75,9 +75,9 @@ public class SmUtil {
 	 * 创建SM2算法对象<br>
 	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
-	 * 
+	 *
 	 * @param privateKey 私钥
-	 * @param publicKey 公钥
+	 * @param publicKey  公钥
 	 * @return {@link SM2}
 	 */
 	public static SM2 sm2(byte[] privateKey, byte[] publicKey) {
@@ -89,7 +89,7 @@ public class SmUtil {
 	 * 例:<br>
 	 * SM3加密:sm3().digest(data)<br>
 	 * SM3加密并转为16进制字符串:sm3().digestHex(data)<br>
-	 * 
+	 *
 	 * @return {@link SM3}
 	 */
 	public static SM3 sm3() {
@@ -98,7 +98,7 @@ public class SmUtil {
 
 	/**
 	 * SM3加密,生成16进制SM3字符串<br>
-	 * 
+	 *
 	 * @param data 数据
 	 * @return SM3字符串
 	 */
@@ -108,7 +108,7 @@ public class SmUtil {
 
 	/**
 	 * SM3加密,生成16进制SM3字符串<br>
-	 * 
+	 *
 	 * @param data 数据
 	 * @return SM3字符串
 	 */
@@ -118,7 +118,7 @@ public class SmUtil {
 
 	/**
 	 * SM3加密文件,生成16进制SM3字符串<br>
-	 * 
+	 *
 	 * @param dataFile 被加密文件
 	 * @return SM3字符串
 	 */
@@ -129,12 +129,12 @@ public class SmUtil {
 	/**
 	 * SM4加密,生成随机KEY。注意解密时必须使用相同 {@link SymmetricCrypto}对象或者使用相同KEY<br>
 	 * 例:
-	 * 
+	 *
 	 * <pre>
 	 * SM4加密:sm4().encrypt(data)
 	 * SM4解密:sm4().decrypt(data)
 	 * </pre>
-	 * 
+	 *
 	 * @return {@link SymmetricCrypto}
 	 */
 	public static SM4 sm4() {
@@ -144,12 +144,12 @@ public class SmUtil {
 	/**
 	 * SM4加密<br>
 	 * 例:
-	 * 
+	 *
 	 * <pre>
 	 * SM4加密:sm4(key).encrypt(data)
 	 * SM4解密:sm4(key).decrypt(data)
 	 * </pre>
-	 * 
+	 *
 	 * @param key 密钥
 	 * @return {@link SymmetricCrypto}
 	 */
@@ -159,8 +159,8 @@ public class SmUtil {
 
 	/**
 	 * bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2
-	 * 
-	 * @param c1c2c3 加密后的bytes,顺序为C1C2C3
+	 *
+	 * @param c1c2c3             加密后的bytes,顺序为C1C2C3
 	 * @param ecDomainParameters {@link ECDomainParameters}
 	 * @return 加密后的bytes,顺序为C1C3C2
 	 */
@@ -177,8 +177,8 @@ public class SmUtil {
 
 	/**
 	 * bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密
-	 * 
-	 * @param c1c3c2 加密后的bytes,顺序为C1C3C2
+	 *
+	 * @param c1c3c2             加密后的bytes,顺序为C1C3C2
 	 * @param ecDomainParameters {@link ECDomainParameters}
 	 * @return c1c2c3 加密后的bytes,顺序为C1C2C3
 	 */
@@ -196,7 +196,7 @@ public class SmUtil {
 	/**
 	 * BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s<br>
 	 * 来自:https://blog.csdn.net/pridas/article/details/86118774
-	 * 
+	 *
 	 * @param rsDer rs in asn1 format
 	 * @return sign result in plain byte array
 	 * @since 4.5.0
@@ -208,13 +208,14 @@ public class SmUtil {
 		byte[] result = new byte[RS_LEN * 2];
 		System.arraycopy(r, 0, result, 0, r.length);
 		System.arraycopy(s, 0, result, RS_LEN, s.length);
+
 		return result;
 	}
 
 	/**
 	 * BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式<br>
 	 * 来自:https://blog.csdn.net/pridas/article/details/86118774
-	 * 
+	 *
 	 * @param sign in plain byte array
 	 * @return rs result in asn1 format
 	 * @since 4.5.0
@@ -237,7 +238,7 @@ public class SmUtil {
 
 	/**
 	 * 创建HmacSM3算法的{@link MacEngine}
-	 * 
+	 *
 	 * @param key 密钥
 	 * @return {@link MacEngine}
 	 * @since 4.5.13
@@ -248,7 +249,7 @@ public class SmUtil {
 
 	/**
 	 * HmacSM3算法实现
-	 * 
+	 *
 	 * @param key 密钥
 	 * @return {@link HMac} 对象,调用digestXXX即可
 	 * @since 4.5.13
@@ -258,9 +259,10 @@ public class SmUtil {
 	}
 
 	// -------------------------------------------------------------------------------------------------------- Private method start
+
 	/**
 	 * BigInteger转固定长度bytes
-	 * 
+	 *
 	 * @param rOrS {@link BigInteger}
 	 * @return 固定长度bytes
 	 * @since 4.5.0

+ 64 - 35
hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/BaseAsymmetric.java

@@ -1,5 +1,10 @@
 package cn.hutool.crypto.asymmetric;
 
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.crypto.CryptoException;
+import cn.hutool.crypto.SecureUtil;
+
 import java.security.Key;
 import java.security.KeyPair;
 import java.security.PrivateKey;
@@ -7,37 +12,42 @@ import java.security.PublicKey;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
-import cn.hutool.core.codec.Base64;
-import cn.hutool.crypto.CryptoException;
-import cn.hutool.crypto.SecureUtil;
-
 /**
  * 非对称基础,提供锁、私钥和公钥的持有
- * 
+ *
  * @author Looly
  * @since 3.3.0
  */
-public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
+public class BaseAsymmetric<T extends BaseAsymmetric<T>> {
 
-	/** 算法 */
+	/**
+	 * 算法
+	 */
 	protected String algorithm;
-	/** 公钥 */
+	/**
+	 * 公钥
+	 */
 	protected PublicKey publicKey;
-	/** 私钥 */
+	/**
+	 * 私钥
+	 */
 	protected PrivateKey privateKey;
-	/** 锁 */
+	/**
+	 * 锁
+	 */
 	protected final Lock lock = new ReentrantLock();
 
 	// ------------------------------------------------------------------ Constructor start
+
 	/**
 	 * 构造
-	 * 
+	 * <p>
 	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
-	 * 
-	 * @param algorithm 算法
+	 *
+	 * @param algorithm  算法
 	 * @param privateKey 私钥
-	 * @param publicKey 公钥
+	 * @param publicKey  公钥
 	 * @since 3.1.1
 	 */
 	public BaseAsymmetric(String algorithm, PrivateKey privateKey, PublicKey publicKey) {
@@ -49,10 +59,10 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
 	 * 初始化<br>
 	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密(签名)或者解密(校验)
-	 * 
-	 * @param algorithm 算法
+	 *
+	 * @param algorithm  算法
 	 * @param privateKey 私钥
-	 * @param publicKey 公钥
+	 * @param publicKey  公钥
 	 * @return this
 	 */
 	@SuppressWarnings("unchecked")
@@ -74,7 +84,7 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
 
 	/**
 	 * 生成公钥和私钥
-	 * 
+	 *
 	 * @return this
 	 */
 	@SuppressWarnings("unchecked")
@@ -86,9 +96,10 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
 	}
 
 	// --------------------------------------------------------------------------------- Getters and Setters
+
 	/**
 	 * 获得公钥
-	 * 
+	 *
 	 * @return 获得公钥
 	 */
 	public PublicKey getPublicKey() {
@@ -97,7 +108,7 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
 
 	/**
 	 * 获得公钥
-	 * 
+	 *
 	 * @return 获得公钥
 	 */
 	public String getPublicKeyBase64() {
@@ -107,7 +118,7 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
 
 	/**
 	 * 设置公钥
-	 * 
+	 *
 	 * @param publicKey 公钥
 	 * @return this
 	 */
@@ -119,7 +130,7 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
 
 	/**
 	 * 获得私钥
-	 * 
+	 *
 	 * @return 获得私钥
 	 */
 	public PrivateKey getPrivateKey() {
@@ -128,7 +139,7 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
 
 	/**
 	 * 获得私钥
-	 * 
+	 *
 	 * @return 获得私钥
 	 */
 	public String getPrivateKeyBase64() {
@@ -137,7 +148,7 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
 
 	/**
 	 * 设置私钥
-	 * 
+	 *
 	 * @param privateKey 私钥
 	 * @return this
 	 */
@@ -148,23 +159,41 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
 	}
 
 	/**
+	 * 设置密钥,可以是公钥{@link PublicKey}或者私钥{@link PrivateKey}
+	 *
+	 * @param key 密钥,可以是公钥{@link PublicKey}或者私钥{@link PrivateKey}
+	 * @return this
+	 * @since 5.2.0
+	 */
+	public T setKey(Key key) {
+		Assert.notNull(key, "key must be not null !");
+
+		if (key instanceof PublicKey) {
+			return setPublicKey((PublicKey) key);
+		} else if (key instanceof PrivateKey) {
+			return setPrivateKey((PrivateKey) key);
+		}
+		throw new CryptoException("Unsupported key type: {}", key.getClass());
+	}
+
+	/**
 	 * 根据密钥类型获得相应密钥
-	 * 
+	 *
 	 * @param type 类型 {@link KeyType}
 	 * @return {@link Key}
 	 */
 	protected Key getKeyByType(KeyType type) {
 		switch (type) {
-		case PrivateKey:
-			if (null == this.privateKey) {
-				throw new NullPointerException("Private key must not null when use it !");
-			}
-			return this.privateKey;
-		case PublicKey:
-			if (null == this.publicKey) {
-				throw new NullPointerException("Public key must not null when use it !");
-			}
-			return this.publicKey;
+			case PrivateKey:
+				if (null == this.privateKey) {
+					throw new NullPointerException("Private key must not null when use it !");
+				}
+				return this.privateKey;
+			case PublicKey:
+				if (null == this.publicKey) {
+					throw new NullPointerException("Public key must not null when use it !");
+				}
+				return this.publicKey;
 		}
 		throw new CryptoException("Uknown key type: " + type);
 	}

+ 141 - 40
hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java

@@ -1,5 +1,8 @@
 package cn.hutool.crypto.asymmetric;
 
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.crypto.BCUtil;
 import cn.hutool.crypto.CryptoException;
 import cn.hutool.crypto.SecureUtil;
 import org.bouncycastle.crypto.CipherParameters;
@@ -10,9 +13,7 @@ import org.bouncycastle.crypto.params.ECPublicKeyParameters;
 import org.bouncycastle.crypto.params.ParametersWithID;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.crypto.signers.SM2Signer;
-import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
 
-import java.security.InvalidKeyException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 
@@ -83,7 +84,56 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 	 * @param publicKey  公钥
 	 */
 	public SM2(PrivateKey privateKey, PublicKey publicKey) {
-		super(ALGORITHM_SM2, privateKey, publicKey);
+		this(BCUtil.toParams(privateKey), BCUtil.toParams(publicKey));
+		if (null != privateKey) {
+			this.privateKey = privateKey;
+		}
+		if (null != publicKey) {
+			this.publicKey = publicKey;
+		}
+	}
+
+	/**
+	 * 构造 <br>
+	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
+	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
+	 *
+	 * @param privateKeyHex      私钥16进制
+	 * @param publicKeyPointXHex 公钥X16进制
+	 * @param publicKeyPointYHex 公钥Y16进制
+	 * @since 5.2.0
+	 */
+	public SM2(String privateKeyHex, String publicKeyPointXHex, String publicKeyPointYHex) {
+		this(BCUtil.toSm2Params(privateKeyHex), BCUtil.toSm2Params(publicKeyPointXHex, publicKeyPointYHex));
+	}
+
+	/**
+	 * 构造 <br>
+	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
+	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
+	 *
+	 * @param privateKey      私钥
+	 * @param publicKeyPointX 公钥X
+	 * @param publicKeyPointY 公钥Y
+	 * @since 5.2.0
+	 */
+	public SM2(byte[] privateKey, byte[] publicKeyPointX, byte[] publicKeyPointY) {
+		this(BCUtil.toSm2Params(privateKey), BCUtil.toSm2Params(publicKeyPointX, publicKeyPointY));
+	}
+
+	/**
+	 * 构造 <br>
+	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
+	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
+	 *
+	 * @param privateKeyParams 私钥
+	 * @param publicKeyParams  公钥
+	 */
+	public SM2(ECPrivateKeyParameters privateKeyParams, ECPublicKeyParameters publicKeyParams) {
+		super(ALGORITHM_SM2, null, null);
+		this.privateKeyParams = privateKeyParams;
+		this.publicKeyParams = publicKeyParams;
+		this.init();
 	}
 
 	// ------------------------------------------------------------------ Constructor end
@@ -93,18 +143,22 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密(签名)或者解密(校验)
 	 *
-	 * @param privateKey 私钥
-	 * @param publicKey  公钥
 	 * @return this
 	 */
-	public SM2 init(PrivateKey privateKey, PublicKey publicKey) {
-		return this.init(ALGORITHM_SM2, privateKey, publicKey);
+	public SM2 init() {
+		if (null == this.privateKeyParams && null == this.publicKeyParams) {
+			super.initKeys();
+			this.privateKeyParams = BCUtil.toParams(this.privateKey);
+			this.publicKeyParams = BCUtil.toParams(this.publicKey);
+		}
+		return this;
 	}
 
 	@Override
-	protected SM2 init(String algorithm, PrivateKey privateKey, PublicKey publicKey) {
-		super.init(algorithm, privateKey, publicKey);
-		return initCipherParams();
+	public SM2 initKeys() {
+		// 阻断父类中自动生成密钥对的操作,此操作由本类中进行。
+		// 由于用户可能传入Params而非key,因此此时key必定为null,故此不再生成
+		return this;
 	}
 
 	// --------------------------------------------------------------------------------- Encrypt
@@ -205,6 +259,16 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 	/**
 	 * 用私钥对信息生成数字签名
 	 *
+	 * @param dataHex 被签名的数据数据
+	 * @return 签名
+	 */
+	public String signHex(String dataHex) {
+		return signHex(dataHex, null);
+	}
+
+	/**
+	 * 用私钥对信息生成数字签名
+	 *
 	 * @param data 加密数据
 	 * @return 签名
 	 */
@@ -215,7 +279,18 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 	/**
 	 * 用私钥对信息生成数字签名
 	 *
-	 * @param data 加密数据
+	 * @param dataHex 被签名的数据数据
+	 * @param idHex   可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
+	 * @return 签名
+	 */
+	public String signHex(String dataHex, String idHex) {
+		return HexUtil.encodeHexStr(sign(HexUtil.decodeHex(dataHex), HexUtil.decodeHex(idHex)));
+	}
+
+	/**
+	 * 用私钥对信息生成数字签名
+	 *
+	 * @param data 被签名的数据数据
 	 * @param id   可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
 	 * @return 签名
 	 */
@@ -230,7 +305,7 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 			signer.init(true, param);
 			signer.update(data, 0, data.length);
 			return signer.generateSignature();
-		} catch (Exception e) {
+		} catch (org.bouncycastle.crypto.CryptoException e) {
 			throw new CryptoException(e);
 		} finally {
 			lock.unlock();
@@ -240,7 +315,19 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 	/**
 	 * 用公钥检验数字签名的合法性
 	 *
-	 * @param data 数据
+	 * @param dataHex 数据签名后的数据
+	 * @param signHex 签名
+	 * @return 是否验证通过
+	 * @since 5.2.0
+	 */
+	public boolean verifyHex(String dataHex, String signHex) {
+		return verifyHex(dataHex, signHex, null);
+	}
+
+	/**
+	 * 用公钥检验数字签名的合法性
+	 *
+	 * @param data 签名后的数据
 	 * @param sign 签名
 	 * @return 是否验证通过
 	 */
@@ -251,7 +338,20 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 	/**
 	 * 用公钥检验数字签名的合法性
 	 *
-	 * @param data 数据
+	 * @param dataHex 数据签名后的数据的Hex值
+	 * @param signHex 签名的Hex值
+	 * @param idHex   ID的Hex值
+	 * @return 是否验证通过
+	 * @since 5.2.0
+	 */
+	public boolean verifyHex(String dataHex, String signHex, String idHex) {
+		return verify(HexUtil.decodeHex(dataHex), HexUtil.decodeHex(signHex), HexUtil.decodeHex(idHex));
+	}
+
+	/**
+	 * 用公钥检验数字签名的合法性
+	 *
+	 * @param data 数据签名后的数据
 	 * @param sign 签名
 	 * @param id   可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
 	 * @return 是否验证通过
@@ -267,8 +367,6 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 			signer.init(false, param);
 			signer.update(data, 0, data.length);
 			return signer.verifySignature(sign);
-		} catch (Exception e) {
-			throw new CryptoException(e);
 		} finally {
 			lock.unlock();
 		}
@@ -279,24 +377,45 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 		super.setPrivateKey(privateKey);
 
 		// 重新初始化密钥参数,防止重新设置密钥时导致密钥无法更新
-		this.privateKeyParams = null;
-		initCipherParams();
+		this.privateKeyParams = BCUtil.toParams(privateKey);
 
 		return this;
 	}
 
+	/**
+	 * 设置私钥参数
+	 *
+	 * @param privateKeyParams 私钥参数
+	 * @return this
+	 * @since 5.2.0
+	 */
+	public SM2 setPrivateKeyParams(ECPrivateKeyParameters privateKeyParams) {
+		this.privateKeyParams = privateKeyParams;
+		return this;
+	}
+
 	@Override
 	public SM2 setPublicKey(PublicKey publicKey) {
 		super.setPublicKey(publicKey);
 
 		// 重新初始化密钥参数,防止重新设置密钥时导致密钥无法更新
-		this.publicKeyParams = null;
-		initCipherParams();
+		this.publicKeyParams = BCUtil.toParams(publicKey);
 
 		return this;
 	}
 
 	/**
+	 * 设置公钥参数
+	 *
+	 * @param publicKeyParams 公钥参数
+	 * @return this
+	 */
+	public SM2 setPublicKeyParams(ECPublicKeyParameters publicKeyParams) {
+		this.publicKeyParams = publicKeyParams;
+		return this;
+	}
+
+	/**
 	 * 设置加密类型
 	 *
 	 * @param mode {@link SM2Engine.Mode}
@@ -313,26 +432,6 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 	// ------------------------------------------------------------------------------------------------------------------------- Private method start
 
 	/**
-	 * 初始化加密解密参数(包括私钥和公钥参数)
-	 *
-	 * @return this
-	 */
-	private SM2 initCipherParams() {
-		try {
-			if (null != this.publicKey) {
-				this.publicKeyParams = (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(this.publicKey);
-			}
-			if (null != privateKey) {
-				this.privateKeyParams = (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(this.privateKey);
-			}
-		} catch (InvalidKeyException e) {
-			throw new CryptoException(e);
-		}
-
-		return this;
-	}
-
-	/**
 	 * 获取密钥类型对应的加密参数对象{@link CipherParameters}
 	 *
 	 * @param keyType Key类型枚举,包括私钥或公钥
@@ -341,8 +440,10 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
 	private CipherParameters getCipherParameters(KeyType keyType) {
 		switch (keyType) {
 			case PublicKey:
+				Assert.notNull(this.publicKeyParams, "PublicKey must be not null !");
 				return this.publicKeyParams;
 			case PrivateKey:
+				Assert.notNull(this.privateKeyParams, "PrivateKey must be not null !");
 				return this.privateKeyParams;
 		}
 

+ 2 - 2
hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/Sign.java

@@ -34,7 +34,7 @@ public class Sign extends BaseAsymmetric<Sign> {
 	 * @param algorithm {@link SignAlgorithm}
 	 */
 	public Sign(SignAlgorithm algorithm) {
-		this(algorithm, (byte[]) null, (byte[]) null);
+		this(algorithm, null, (byte[]) null);
 	}
 
 	/**
@@ -43,7 +43,7 @@ public class Sign extends BaseAsymmetric<Sign> {
 	 * @param algorithm 算法
 	 */
 	public Sign(String algorithm) {
-		this(algorithm, (byte[]) null, (byte[]) null);
+		this(algorithm, null, (byte[]) null);
 	}
 
 	/**

+ 7 - 3
hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java

@@ -2,13 +2,17 @@ package cn.hutool.crypto.test;
 
 import cn.hutool.core.lang.Assert;
 import cn.hutool.crypto.BCUtil;
-import cn.hutool.crypto.SmUtil;
 import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
 import org.bouncycastle.crypto.params.ECPublicKeyParameters;
 import org.junit.Test;
 
 public class BCUtilTest {
 
+	@Test
+	public void decodeECPointTest(){
+
+	}
+
 	/**
 	 * 密钥生成来自:https://i.goto327.top/CryptTools/SM2.aspx?tdsourcetag=s_pctim_aiomsg
 	 */
@@ -17,7 +21,7 @@ public class BCUtilTest {
 		String x = "706AD9DAA3E5CEAC3DA59F583429E8043BAFC576BE10092C4EA4D8E19846CA62";
 		String y = "F7E938B02EED7280277493B8556E5B01CB436E018A562DFDC53342BF41FDF728";
 
-		final ECPublicKeyParameters keyParameters = BCUtil.toParams(x, y, SmUtil.CURVE, SmUtil.DOMAIN_PARAMS);
+		final ECPublicKeyParameters keyParameters = BCUtil.toSm2Params(x, y);
 		Assert.notNull(keyParameters);
 	}
 
@@ -25,7 +29,7 @@ public class BCUtilTest {
 	public void createECPrivateKeyParametersTest() {
 		String privateKeyHex = "5F6CA5BB044C40ED2355F0372BF72A5B3AE6943712F9FDB7C1FFBAECC06F3829";
 
-		final ECPrivateKeyParameters keyParameters = BCUtil.toParams(privateKeyHex, SmUtil.DOMAIN_PARAMS);
+		final ECPrivateKeyParameters keyParameters = BCUtil.toSm2Params(privateKeyHex);
 		Assert.notNull(keyParameters);
 	}
 }

+ 27 - 0
hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java

@@ -122,6 +122,17 @@ public class SM2Test {
 	}
 
 	@Test
+	public void sm2SignAndVerifyHexTest() {
+		String content = "我是Hanley.";
+
+		final SM2 sm2 = SmUtil.sm2();
+
+		String sign = sm2.signHex(HexUtil.encodeHexStr(content));
+		boolean verify = sm2.verifyHex(HexUtil.encodeHexStr(content), sign);
+		Assert.assertTrue(verify);
+	}
+
+	@Test
 	public void sm2SignAndVerifyUseKeyTest() {
 		String content = "我是Hanley.";
 
@@ -149,4 +160,20 @@ public class SM2Test {
 		Assert.assertEquals(HexUtil.encodeHexStr(publicKey.getEncoded()), HexUtil.encodeHexStr(Hexdecode.getEncoded()));
 		Assert.assertEquals(HexUtil.encodeHexStr(publicKey.getEncoded()), HexUtil.encodeHexStr(B64decode.getEncoded()));
 	}
+
+	@Test
+	public void sm2WithPointTest(){
+		String d = "FAB8BBE670FAE338C9E9382B9FB6485225C11A3ECB84C938F10F20A93B6215F0";
+		String x = "9EF573019D9A03B16B0BE44FC8A5B4E8E098F56034C97B312282DD0B4810AFC3";
+		String y = "CC759673ED0FC9B9DC7E6FA38F0E2B121E02654BF37EA6B63FAF2A0D6013EADF";
+
+		String data = "434477813974bf58f94bcf760833c2b40f77a5fc360485b0b9ed1bd9682edb45";
+		String id = "31323334353637383132333435363738";
+
+		final SM2 sm2 = new SM2(d, x, y);
+
+		final String sign = sm2.signHex(data, id);
+
+		Assert.assertTrue(sm2.verifyHex(data, sign));
+	}
 }