support x,y for sm2

This commit is contained in:
Looly 2020-03-05 00:49:07 +08:00
parent f37c1d529a
commit f6585adec4
11 changed files with 498 additions and 185 deletions

View File

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

View File

@ -1,6 +1,7 @@
package cn.hutool.core.util; package cn.hutool.core.util;
import java.awt.Color; import java.awt.Color;
import java.math.BigInteger;
import java.nio.charset.Charset; import java.nio.charset.Charset;
/** /**
@ -198,6 +199,7 @@ public class HexUtil {
if (StrUtil.isEmpty(hexStr)) { if (StrUtil.isEmpty(hexStr)) {
return null; return null;
} }
hexStr = StrUtil.removeAll(hexStr, ' ');
return decodeHex(hexStr.toCharArray()); return decodeHex(hexStr.toCharArray());
} }
@ -338,6 +340,19 @@ public class HexUtil {
builder.append(toDigits[low]); builder.append(toDigits[low]);
} }
/**
* Hex16进制字符串转为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 // ---------------------------------------------------------------------------------------- Private method start
/** /**

View File

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

View File

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

View File

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

View File

@ -13,10 +13,9 @@ import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters; 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.Arrays;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
@ -34,20 +33,21 @@ import java.math.BigInteger;
*/ */
public class SmUtil { public class SmUtil {
/* /**
* SM2默认曲线
*/
public static final String SM2_CURVE_NAME = "sm2p256v1";
/**
* SM2推荐曲线参数来自https://github.com/ZZMarquis/gmhelper * SM2推荐曲线参数来自https://github.com/ZZMarquis/gmhelper
*/ */
public static final SM2P256V1Curve CURVE = new SM2P256V1Curve(); public static final ECDomainParameters SM2_DOMAIN_PARAMS;
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());
private final static int RS_LEN = 32; private final static int RS_LEN = 32;
static {
SM2_DOMAIN_PARAMS = BCUtil.toDomainParams(GMNamedCurves.getByName(SM2_CURVE_NAME));
}
/** /**
* 创建SM2算法对象<br> * 创建SM2算法对象<br>
* 生成新的私钥公钥对 * 生成新的私钥公钥对
@ -64,7 +64,7 @@ public class SmUtil {
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密 * 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
* *
* @param privateKeyStr 私钥Hex或Base64表示 * @param privateKeyStr 私钥Hex或Base64表示
* @param publicKeyStr 公钥Hex或Base64表示 * @param publicKeyStr 公钥Hex或Base64表示
* @return {@link SM2} * @return {@link SM2}
*/ */
public static SM2 sm2(String privateKeyStr, String publicKeyStr) { public static SM2 sm2(String privateKeyStr, String publicKeyStr) {
@ -77,7 +77,7 @@ public class SmUtil {
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密 * 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
* *
* @param privateKey 私钥 * @param privateKey 私钥
* @param publicKey 公钥 * @param publicKey 公钥
* @return {@link SM2} * @return {@link SM2}
*/ */
public static SM2 sm2(byte[] privateKey, byte[] publicKey) { public static SM2 sm2(byte[] privateKey, byte[] publicKey) {
@ -160,7 +160,7 @@ public class SmUtil {
/** /**
* bc加解密使用旧标c1||c2||c3此方法在加密后调用将结果转化为c1||c3||c2 * bc加解密使用旧标c1||c2||c3此方法在加密后调用将结果转化为c1||c3||c2
* *
* @param c1c2c3 加密后的bytes顺序为C1C2C3 * @param c1c2c3 加密后的bytes顺序为C1C2C3
* @param ecDomainParameters {@link ECDomainParameters} * @param ecDomainParameters {@link ECDomainParameters}
* @return 加密后的bytes顺序为C1C3C2 * @return 加密后的bytes顺序为C1C3C2
*/ */
@ -178,7 +178,7 @@ public class SmUtil {
/** /**
* bc加解密使用旧标c1||c3||c2此方法在解密前调用将密文转化为c1||c2||c3再去解密 * bc加解密使用旧标c1||c3||c2此方法在解密前调用将密文转化为c1||c2||c3再去解密
* *
* @param c1c3c2 加密后的bytes顺序为C1C3C2 * @param c1c3c2 加密后的bytes顺序为C1C3C2
* @param ecDomainParameters {@link ECDomainParameters} * @param ecDomainParameters {@link ECDomainParameters}
* @return c1c2c3 加密后的bytes顺序为C1C2C3 * @return c1c2c3 加密后的bytes顺序为C1C2C3
*/ */
@ -208,6 +208,7 @@ public class SmUtil {
byte[] result = new byte[RS_LEN * 2]; byte[] result = new byte[RS_LEN * 2];
System.arraycopy(r, 0, result, 0, r.length); System.arraycopy(r, 0, result, 0, r.length);
System.arraycopy(s, 0, result, RS_LEN, s.length); System.arraycopy(s, 0, result, RS_LEN, s.length);
return result; return result;
} }
@ -258,6 +259,7 @@ public class SmUtil {
} }
// -------------------------------------------------------------------------------------------------------- Private method start // -------------------------------------------------------------------------------------------------------- Private method start
/** /**
* BigInteger转固定长度bytes * BigInteger转固定长度bytes
* *

View File

@ -1,5 +1,10 @@
package cn.hutool.crypto.asymmetric; 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.Key;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -7,37 +12,42 @@ import java.security.PublicKey;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.SecureUtil;
/** /**
* 非对称基础提供锁私钥和公钥的持有 * 非对称基础提供锁私钥和公钥的持有
* *
* @author Looly * @author Looly
* @since 3.3.0 * @since 3.3.0
*/ */
public class BaseAsymmetric<T extends BaseAsymmetric<T>>{ public class BaseAsymmetric<T extends BaseAsymmetric<T>> {
/** 算法 */ /**
* 算法
*/
protected String algorithm; protected String algorithm;
/** 公钥 */ /**
* 公钥
*/
protected PublicKey publicKey; protected PublicKey publicKey;
/** 私钥 */ /**
* 私钥
*/
protected PrivateKey privateKey; protected PrivateKey privateKey;
/** 锁 */ /**
*
*/
protected final Lock lock = new ReentrantLock(); protected final Lock lock = new ReentrantLock();
// ------------------------------------------------------------------ Constructor start // ------------------------------------------------------------------ Constructor start
/** /**
* 构造 * 构造
* * <p>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br> * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密 * 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
* *
* @param algorithm 算法 * @param algorithm 算法
* @param privateKey 私钥 * @param privateKey 私钥
* @param publicKey 公钥 * @param publicKey 公钥
* @since 3.1.1 * @since 3.1.1
*/ */
public BaseAsymmetric(String algorithm, PrivateKey privateKey, PublicKey publicKey) { public BaseAsymmetric(String algorithm, PrivateKey privateKey, PublicKey publicKey) {
@ -50,9 +60,9 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br> * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密签名或者解密校验 * 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密签名或者解密校验
* *
* @param algorithm 算法 * @param algorithm 算法
* @param privateKey 私钥 * @param privateKey 私钥
* @param publicKey 公钥 * @param publicKey 公钥
* @return this * @return this
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -86,6 +96,7 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
} }
// --------------------------------------------------------------------------------- Getters and Setters // --------------------------------------------------------------------------------- Getters and Setters
/** /**
* 获得公钥 * 获得公钥
* *
@ -147,6 +158,24 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
return (T) this; return (T) this;
} }
/**
* 设置密钥可以是公钥{@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());
}
/** /**
* 根据密钥类型获得相应密钥 * 根据密钥类型获得相应密钥
* *
@ -155,16 +184,16 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
*/ */
protected Key getKeyByType(KeyType type) { protected Key getKeyByType(KeyType type) {
switch (type) { switch (type) {
case PrivateKey: case PrivateKey:
if (null == this.privateKey) { if (null == this.privateKey) {
throw new NullPointerException("Private key must not null when use it !"); throw new NullPointerException("Private key must not null when use it !");
} }
return this.privateKey; return this.privateKey;
case PublicKey: case PublicKey:
if (null == this.publicKey) { if (null == this.publicKey) {
throw new NullPointerException("Public key must not null when use it !"); throw new NullPointerException("Public key must not null when use it !");
} }
return this.publicKey; return this.publicKey;
} }
throw new CryptoException("Uknown key type: " + type); throw new CryptoException("Uknown key type: " + type);
} }

View File

@ -1,5 +1,8 @@
package cn.hutool.crypto.asymmetric; 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.CryptoException;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import org.bouncycastle.crypto.CipherParameters; 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.ParametersWithID;
import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer; 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.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
@ -83,7 +84,56 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
* @param publicKey 公钥 * @param publicKey 公钥
*/ */
public SM2(PrivateKey privateKey, PublicKey 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 // ------------------------------------------------------------------ Constructor end
@ -93,18 +143,22 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br> * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密签名或者解密校验 * 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密签名或者解密校验
* *
* @param privateKey 私钥
* @param publicKey 公钥
* @return this * @return this
*/ */
public SM2 init(PrivateKey privateKey, PublicKey publicKey) { public SM2 init() {
return this.init(ALGORITHM_SM2, privateKey, publicKey); if (null == this.privateKeyParams && null == this.publicKeyParams) {
super.initKeys();
this.privateKeyParams = BCUtil.toParams(this.privateKey);
this.publicKeyParams = BCUtil.toParams(this.publicKey);
}
return this;
} }
@Override @Override
protected SM2 init(String algorithm, PrivateKey privateKey, PublicKey publicKey) { public SM2 initKeys() {
super.init(algorithm, privateKey, publicKey); // 阻断父类中自动生成密钥对的操作此操作由本类中进行
return initCipherParams(); // 由于用户可能传入Params而非key因此此时key必定为null故此不再生成
return this;
} }
// --------------------------------------------------------------------------------- Encrypt // --------------------------------------------------------------------------------- Encrypt
@ -202,6 +256,16 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
} }
// --------------------------------------------------------------------------------- Sign and Verify // --------------------------------------------------------------------------------- Sign and Verify
/**
* 用私钥对信息生成数字签名
*
* @param dataHex 被签名的数据数据
* @return 签名
*/
public String signHex(String dataHex) {
return signHex(dataHex, null);
}
/** /**
* 用私钥对信息生成数字签名 * 用私钥对信息生成数字签名
* *
@ -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() * @param id 可以为null若为null则默认withId为字节数组:"1234567812345678".getBytes()
* @return 签名 * @return 签名
*/ */
@ -230,7 +305,7 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
signer.init(true, param); signer.init(true, param);
signer.update(data, 0, data.length); signer.update(data, 0, data.length);
return signer.generateSignature(); return signer.generateSignature();
} catch (Exception e) { } catch (org.bouncycastle.crypto.CryptoException e) {
throw new CryptoException(e); throw new CryptoException(e);
} finally { } finally {
lock.unlock(); 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 签名 * @param sign 签名
* @return 是否验证通过 * @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 sign 签名
* @param id 可以为null若为null则默认withId为字节数组:"1234567812345678".getBytes() * @param id 可以为null若为null则默认withId为字节数组:"1234567812345678".getBytes()
* @return 是否验证通过 * @return 是否验证通过
@ -267,8 +367,6 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
signer.init(false, param); signer.init(false, param);
signer.update(data, 0, data.length); signer.update(data, 0, data.length);
return signer.verifySignature(sign); return signer.verifySignature(sign);
} catch (Exception e) {
throw new CryptoException(e);
} finally { } finally {
lock.unlock(); lock.unlock();
} }
@ -279,23 +377,44 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
super.setPrivateKey(privateKey); super.setPrivateKey(privateKey);
// 重新初始化密钥参数防止重新设置密钥时导致密钥无法更新 // 重新初始化密钥参数防止重新设置密钥时导致密钥无法更新
this.privateKeyParams = null; this.privateKeyParams = BCUtil.toParams(privateKey);
initCipherParams();
return this; return this;
} }
/**
* 设置私钥参数
*
* @param privateKeyParams 私钥参数
* @return this
* @since 5.2.0
*/
public SM2 setPrivateKeyParams(ECPrivateKeyParameters privateKeyParams) {
this.privateKeyParams = privateKeyParams;
return this;
}
@Override @Override
public SM2 setPublicKey(PublicKey publicKey) { public SM2 setPublicKey(PublicKey publicKey) {
super.setPublicKey(publicKey); super.setPublicKey(publicKey);
// 重新初始化密钥参数防止重新设置密钥时导致密钥无法更新 // 重新初始化密钥参数防止重新设置密钥时导致密钥无法更新
this.publicKeyParams = null; this.publicKeyParams = BCUtil.toParams(publicKey);
initCipherParams();
return this; return this;
} }
/**
* 设置公钥参数
*
* @param publicKeyParams 公钥参数
* @return this
*/
public SM2 setPublicKeyParams(ECPublicKeyParameters publicKeyParams) {
this.publicKeyParams = publicKeyParams;
return this;
}
/** /**
* 设置加密类型 * 设置加密类型
* *
@ -312,26 +431,6 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
// ------------------------------------------------------------------------------------------------------------------------- Private method start // ------------------------------------------------------------------------------------------------------------------------- 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} * 获取密钥类型对应的加密参数对象{@link CipherParameters}
* *
@ -341,8 +440,10 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
private CipherParameters getCipherParameters(KeyType keyType) { private CipherParameters getCipherParameters(KeyType keyType) {
switch (keyType) { switch (keyType) {
case PublicKey: case PublicKey:
Assert.notNull(this.publicKeyParams, "PublicKey must be not null !");
return this.publicKeyParams; return this.publicKeyParams;
case PrivateKey: case PrivateKey:
Assert.notNull(this.privateKeyParams, "PrivateKey must be not null !");
return this.privateKeyParams; return this.privateKeyParams;
} }

View File

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

View File

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

View File

@ -121,6 +121,17 @@ public class SM2Test {
Assert.assertTrue(verify); Assert.assertTrue(verify);
} }
@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 @Test
public void sm2SignAndVerifyUseKeyTest() { public void sm2SignAndVerifyUseKeyTest() {
String content = "我是Hanley."; 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(Hexdecode.getEncoded()));
Assert.assertEquals(HexUtil.encodeHexStr(publicKey.getEncoded()), HexUtil.encodeHexStr(B64decode.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));
}
} }