From f6585adec42dcfb7f43f4d203256a09916fd09a7 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 5 Mar 2020 00:49:07 +0800 Subject: [PATCH] support x,y for sm2 --- CHANGELOG.md | 1 + .../java/cn/hutool/core/util/HexUtil.java | 15 + .../java/cn/hutool/core/util/HexUtilTest.java | 7 + .../main/java/cn/hutool/crypto/BCUtil.java | 267 +++++++++++++----- .../main/java/cn/hutool/crypto/KeyUtil.java | 2 +- .../main/java/cn/hutool/crypto/SmUtil.java | 70 ++--- .../crypto/asymmetric/BaseAsymmetric.java | 99 ++++--- .../java/cn/hutool/crypto/asymmetric/SM2.java | 181 +++++++++--- .../cn/hutool/crypto/asymmetric/Sign.java | 4 +- .../cn/hutool/crypto/test/BCUtilTest.java | 10 +- .../java/cn/hutool/crypto/test/SM2Test.java | 27 ++ 11 files changed, 498 insertions(+), 185 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cf36bf52..af4b4a494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ * 【crypto】 增加PemUtil工具类 * 【dfa 】 WordTree增加Filter,支持自定义特殊字符过滤器 * 【poi 】 对于POI依赖升级到4.1.2 +* 【crypto】 增加国密SM2验签密钥格式支持(issue#686@Github) ### Bug修复 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HexUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HexUtil.java index e039a88ea..903820116 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HexUtil.java +++ b/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 /** diff --git a/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java index 9c620d63b..99553f241 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java +++ b/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())); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java index d3d6cb9a9..0098e86b7 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java +++ b/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)
- * 见: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); + } + } + + /** + * 将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) { diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index defd400c0..aad2a8a73 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -93,7 +93,7 @@ public class KeyUtil { * Default SM2 curve * */ - public static final String SM2_DEFAULT_CURVE = "sm2p256v1"; + public static final String SM2_DEFAULT_CURVE = SmUtil.SM2_CURVE_NAME; /** * 生成 {@link SecretKey},仅用于对称加密和摘要算法密钥生成 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java index 09a189d51..0c292b38b 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java +++ b/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国密算法工具类
* 此工具类依赖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算法对象
* 生成新的私钥公钥对 - * + * * @return {@link SM2} */ public static SM2 sm2() { @@ -62,9 +62,9 @@ public class SmUtil { * 创建SM2算法对象
* 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * + * * @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算法对象
* 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * + * * @param privateKey 私钥 - * @param publicKey 公钥 + * @param publicKey 公钥 * @return {@link SM2} */ public static SM2 sm2(byte[] privateKey, byte[] publicKey) { @@ -89,7 +89,7 @@ public class SmUtil { * 例:
* SM3加密:sm3().digest(data)
* SM3加密并转为16进制字符串:sm3().digestHex(data)
- * + * * @return {@link SM3} */ public static SM3 sm3() { @@ -98,7 +98,7 @@ public class SmUtil { /** * SM3加密,生成16进制SM3字符串
- * + * * @param data 数据 * @return SM3字符串 */ @@ -108,7 +108,7 @@ public class SmUtil { /** * SM3加密,生成16进制SM3字符串
- * + * * @param data 数据 * @return SM3字符串 */ @@ -118,7 +118,7 @@ public class SmUtil { /** * SM3加密文件,生成16进制SM3字符串
- * + * * @param dataFile 被加密文件 * @return SM3字符串 */ @@ -129,12 +129,12 @@ public class SmUtil { /** * SM4加密,生成随机KEY。注意解密时必须使用相同 {@link SymmetricCrypto}对象或者使用相同KEY
* 例: - * + * *
 	 * SM4加密:sm4().encrypt(data)
 	 * SM4解密:sm4().decrypt(data)
 	 * 
- * + * * @return {@link SymmetricCrypto} */ public static SM4 sm4() { @@ -144,12 +144,12 @@ public class SmUtil { /** * SM4加密
* 例: - * + * *
 	 * SM4加密:sm4(key).encrypt(data)
 	 * SM4解密:sm4(key).decrypt(data)
 	 * 
- * + * * @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
* 来自: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格式
* 来自: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 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/BaseAsymmetric.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/BaseAsymmetric.java index 32bc0aee6..023d9b9b0 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/BaseAsymmetric.java +++ b/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>{ +public class BaseAsymmetric> { - /** 算法 */ + /** + * 算法 + */ protected String algorithm; - /** 公钥 */ + /** + * 公钥 + */ protected PublicKey publicKey; - /** 私钥 */ + /** + * 私钥 + */ protected PrivateKey privateKey; - /** 锁 */ + /** + * 锁 + */ protected final Lock lock = new ReentrantLock(); // ------------------------------------------------------------------ Constructor start + /** * 构造 - * + *

* 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * - * @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>{ * 初始化
* 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密(签名)或者解密(校验) - * - * @param algorithm 算法 + * + * @param algorithm 算法 * @param privateKey 私钥 - * @param publicKey 公钥 + * @param publicKey 公钥 * @return this */ @SuppressWarnings("unchecked") @@ -74,7 +84,7 @@ public class BaseAsymmetric>{ /** * 生成公钥和私钥 - * + * * @return this */ @SuppressWarnings("unchecked") @@ -86,9 +96,10 @@ public class BaseAsymmetric>{ } // --------------------------------------------------------------------------------- Getters and Setters + /** * 获得公钥 - * + * * @return 获得公钥 */ public PublicKey getPublicKey() { @@ -97,7 +108,7 @@ public class BaseAsymmetric>{ /** * 获得公钥 - * + * * @return 获得公钥 */ public String getPublicKeyBase64() { @@ -107,7 +118,7 @@ public class BaseAsymmetric>{ /** * 设置公钥 - * + * * @param publicKey 公钥 * @return this */ @@ -119,7 +130,7 @@ public class BaseAsymmetric>{ /** * 获得私钥 - * + * * @return 获得私钥 */ public PrivateKey getPrivateKey() { @@ -128,7 +139,7 @@ public class BaseAsymmetric>{ /** * 获得私钥 - * + * * @return 获得私钥 */ public String getPrivateKeyBase64() { @@ -137,7 +148,7 @@ public class BaseAsymmetric>{ /** * 设置私钥 - * + * * @param privateKey 私钥 * @return this */ @@ -147,24 +158,42 @@ public class BaseAsymmetric>{ 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); } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 367f64b34..cf329334a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/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 { * @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; + } + } + + /** + * 构造
+ * 私钥和公钥同时为空时生成一对新的私钥和公钥
+ * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 + * + * @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)); + } + + /** + * 构造
+ * 私钥和公钥同时为空时生成一对新的私钥和公钥
+ * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 + * + * @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)); + } + + /** + * 构造
+ * 私钥和公钥同时为空时生成一对新的私钥和公钥
+ * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 + * + * @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 { * 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密(签名)或者解密(校验) * - * @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 { } // --------------------------------------------------------------------------------- Sign and Verify + /** + * 用私钥对信息生成数字签名 + * + * @param dataHex 被签名的数据数据 + * @return 签名 + */ + public String signHex(String dataHex) { + return signHex(dataHex, null); + } + /** * 用私钥对信息生成数字签名 * @@ -215,7 +279,18 @@ public class SM2 extends AbstractAsymmetricCrypto { /** * 用私钥对信息生成数字签名 * - * @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 { 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 { /** * 用公钥检验数字签名的合法性 * - * @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 { /** * 用公钥检验数字签名的合法性 * - * @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 { 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 { 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 { // ------------------------------------------------------------------------------------------------------------------------- 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 { 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; } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/Sign.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/Sign.java index 81c67a3ae..884b86bb0 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/Sign.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/Sign.java @@ -34,7 +34,7 @@ public class Sign extends BaseAsymmetric { * @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 { * @param algorithm 算法 */ public Sign(String algorithm) { - this(algorithm, (byte[]) null, (byte[]) null); + this(algorithm, null, (byte[]) null); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java index 67989592a..6afd56e99 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java +++ b/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); } } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java index a119ddb23..7772c00c7 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java @@ -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)); + } }