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

View File

@ -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]);
}
/**
* 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
/**

View File

@ -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()));
}
}

View File

@ -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 ECKeyParameters toParams(ECKey ecKey) {
final ECParameterSpec parameterSpec = ecKey.getParameters();
final ECDomainParameters ecDomainParameters = toDomainParameters(parameterSpec);
public static ECDomainParameters toDomainParams(ECParameterSpec parameterSpec) {
return new ECDomainParameters(
parameterSpec.getCurve(),
parameterSpec.getG(),
parameterSpec.getN(),
parameterSpec.getH());
}
if (ecKey instanceof BCECPrivateKey) {
return new ECPrivateKeyParameters(((BCECPrivateKey) ecKey).getD(), ecDomainParameters);
} else if (ecKey instanceof BCECPublicKey) {
return new ECPublicKeyParameters(((BCECPublicKey) ecKey).getQ(), ecDomainParameters);
/**
* 构建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;
}
/**
* 构建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 int getCurveLength(ECKeyParameters ecKey) {
return getCurveLength(ecKey.getParameters());
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 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长度
*
* @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) {

View File

@ -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}仅用于对称加密和摘要算法密钥生成

View File

@ -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

View File

@ -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
*/
@ -147,24 +158,42 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>>{
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());
}
/**
* 根据密钥类型获得相应密钥
*
*
* @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);
}

View File

@ -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
@ -202,6 +256,16 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
}
// --------------------------------------------------------------------------------- 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()
* @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,23 +377,44 @@ 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;
}
/**
* 设置加密类型
*
@ -312,26 +431,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}
*
@ -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;
}

View File

@ -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);
}
/**

View File

@ -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);
}
}

View File

@ -121,6 +121,17 @@ public class SM2Test {
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
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));
}
}