From 3bc5054a9a76966cb26dbeecf1ff0cf4cd7e2a76 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 19 Feb 2021 17:41:49 +0800 Subject: [PATCH] fix sm2 bugs --- CHANGELOG.md | 2 + .../java/cn/hutool/core/util/HexUtil.java | 8 ++-- .../main/java/cn/hutool/crypto/BCUtil.java | 19 ++++++++-- .../main/java/cn/hutool/crypto/ECKeyUtil.java | 5 ++- .../main/java/cn/hutool/crypto/SmUtil.java | 2 +- .../java/cn/hutool/crypto/asymmetric/SM2.java | 21 +++++++++++ .../crypto/test/asymmetric/SM2Test.java | 37 +++++++++++++++++-- 7 files changed, 82 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f1a63c30..f48d23439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,13 @@ * 【crypto 】 PemUtil.readPemKey支持EC(pr#1366@Github) * 【extra 】 Ftp等cd方法增加同步(issue#1397@Github) * 【core 】 StrUtil增加endWithAnyIgnoreCase(issue#I37I0B@Gitee) +* 【crypto 】 Sm2增加getD和getQ方法(issue#I37Z4C@Gitee) ### Bug修复 * 【json 】 JSONUtil.isJson方法改变trim策略,解决特殊空白符导致判断失败问题 * 【json 】 修复SQLEXception导致的栈溢出(issue#1399@Github) * 【extra 】 修复Ftp中异常参数没有传入问题(issue#1397@Github) +* 【crypto 】 修复Sm2使用D构造空指针问题(issue#I37Z4C@Gitee) ------------------------------------------------------------------------------------------------------------- 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 5872918eb..cf59864ed 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 @@ -26,7 +26,7 @@ public class HexUtil { /** * 判断给定字符串是否为16进制数
- * 如果是,需要使用对应数字类型对象的decode方法解码
+ * 如果是,需要使用对应数字类型对象的{@code decode}方法解码
* 例如:{@code Integer.decode}方法解码int类型的16进制数字 * * @param value 值 @@ -74,7 +74,7 @@ public class HexUtil { * 将字节数组转换为十六进制字符数组 * * @param data byte[] - * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 + * @param toLowerCase {@code true} 传换成小写格式 , {@code false} 传换成大写格式 * @return 十六进制char[] */ public static char[] encodeHex(byte[] data, boolean toLowerCase) { @@ -116,7 +116,7 @@ public class HexUtil { * 将字节数组转换为十六进制字符串 * * @param data byte[] - * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 + * @param toLowerCase {@code true} 传换成小写格式 , {@code false} 传换成大写格式 * @return 十六进制String */ public static String encodeHexStr(byte[] data, boolean toLowerCase) { @@ -421,4 +421,4 @@ public class HexUtil { return digit; } // ---------------------------------------------------------------------------------------- Private method end -} \ No newline at end of file +} 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 412e0cca2..918b1086b 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java @@ -41,15 +41,28 @@ public class BCUtil { } /** - * 编码压缩EC公钥(基于BouncyCastle)
+ * 编码压缩EC公钥(基于BouncyCastle),即Q值
* 见:https://www.cnblogs.com/xinzhao/p/8963724.html * * @param publicKey {@link PublicKey},必须为org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey - * @return 压缩得到的X + * @return 压缩得到的Q * @since 4.4.4 */ public static byte[] encodeECPublicKey(PublicKey publicKey) { - return ((BCECPublicKey) publicKey).getQ().getEncoded(true); + return encodeECPublicKey(publicKey, true); + } + + /** + * 编码压缩EC公钥(基于BouncyCastle),即Q值
+ * 见:https://www.cnblogs.com/xinzhao/p/8963724.html + * + * @param publicKey {@link PublicKey},必须为org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey + * @param isCompressed 是否压缩 + * @return 得到的Q + * @since 5.5.9 + */ + public static byte[] encodeECPublicKey(PublicKey publicKey, boolean isCompressed) { + return ((BCECPublicKey) publicKey).getQ().getEncoded(isCompressed); } /** diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/ECKeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/ECKeyUtil.java index 30d5264c7..73b12a8c1 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/ECKeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/ECKeyUtil.java @@ -87,7 +87,7 @@ public class ECKeyUtil { * @param x 公钥X * @param y 公钥Y * @param domainParameters ECDomainParameters - * @return ECPublicKeyParameters + * @return ECPublicKeyParameters,x或y为{@code null}则返回{@code null} */ public static ECPublicKeyParameters toPublicParams(String x, String y, ECDomainParameters domainParameters) { return toPublicParams(SecureUtil.decode(x), SecureUtil.decode(y), domainParameters); @@ -102,6 +102,9 @@ public class ECKeyUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toPublicParams(byte[] xBytes, byte[] yBytes, ECDomainParameters domainParameters) { + if(null == xBytes || null == yBytes){ + return null; + } return toPublicParams(BigIntegers.fromUnsignedByteArray(xBytes), BigIntegers.fromUnsignedByteArray(yBytes), domainParameters); } 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 e5a3b8cac..861768492 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java @@ -26,7 +26,7 @@ import java.math.BigInteger; /** * SM国密算法工具类
- * 此工具类依赖org.bouncycastle:bcpkix-jdk15on + * 此工具类依赖org.bouncycastle:bcprov-jdk15to18 * * @author looly * @since 4.3.2 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 84b0f6e18..e22c9402c 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 @@ -471,6 +471,27 @@ public class SM2 extends AbstractAsymmetricCrypto { return this; } + /** + * 获得私钥D值(编码后的私钥) + * + * @return D值 + * @since 5.5.9 + */ + public byte[] getD() { + return this.privateKeyParams.getD().toByteArray(); + } + + /** + * 获得公钥Q值(编码后的公钥) + * + * @param isCompressed 是否压缩 + * @return Q值 + * @since 5.5.9 + */ + public byte[] getQ(boolean isCompressed) { + return this.publicKeyParams.getQ().getEncoded(isCompressed); + } + // ------------------------------------------------------------------------------------------------------------------------- Private method start /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java index 5d74d706f..0c4bfe152 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java @@ -1,7 +1,6 @@ package cn.hutool.crypto.test.asymmetric; import cn.hutool.core.codec.Base64; -import cn.hutool.core.lang.Console; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; @@ -31,7 +30,6 @@ public class SM2Test { @Test public void generateKeyPairTest() { KeyPair pair = SecureUtil.generateKeyPair("SM2"); - Console.log(HexUtil.encodeHexStr(pair.getPublic().getEncoded())); Assert.assertNotNull(pair.getPrivate()); Assert.assertNotNull(pair.getPublic()); } @@ -114,6 +112,36 @@ public class SM2Test { Assert.assertEquals(text.toString(), decryptStr2); } + @Test + public void sm2SignTest(){ + //需要签名的明文,得到明文对应的字节数组 + byte[] dataBytes = "我是一段测试aaaa".getBytes(); + + //指定的私钥 + String privateKeyHex = "1ebf8b341c695ee456fd1a41b82645724bc25d79935437d30e7e4b0a554baa5e"; + final SM2 sm2 = new SM2(privateKeyHex, null, null); + sm2.usePlainEncoding(); + byte[] sign = sm2.sign(dataBytes, null); + // 64位签名 + Assert.assertEquals(64, sign.length); + } + + @Test + public void sm2VerifyTest(){ + //指定的公钥 + String publicKeyHex = "04db9629dd33ba568e9507add5df6587a0998361a03d3321948b448c653c2c1b7056434884ab6f3d1c529501f166a336e86f045cea10dffe58aa82ea13d7253763"; + //需要加密的明文,得到明文对应的字节数组 + byte[] dataBytes = "我是一段测试aaaa".getBytes(); + //签名值 + String signHex = "2881346e038d2ed706ccdd025f2b1dafa7377d5cf090134b98756fafe084dddbcdba0ab00b5348ed48025195af3f1dda29e819bb66aa9d4d088050ff148482a1"; + + final SM2 sm2 = new SM2(null, ECKeyUtil.toSm2PublicParams(publicKeyHex)); + sm2.usePlainEncoding(); + + boolean verify = sm2.verify(dataBytes, HexUtil.decodeHex(signHex)); + Assert.assertTrue(verify); + } + @Test public void sm2SignAndVerifyTest() { String content = "我是Hanley."; @@ -207,8 +235,12 @@ public class SM2Test { // 生成的签名是64位 sm2.usePlainEncoding(); + String sign = "DCA0E80A7F46C93714B51C3EFC55A922BCEF7ECF0FE9E62B53BA6A7438B543A76C145A452CA9036F3CB70D7E6C67D4D9D7FE114E5367A2F6F5A4D39F2B10F3D6"; Assert.assertTrue(sm2.verifyHex(data, sign)); + + String sign2 = sm2.signHex(data, id); + Assert.assertTrue(sm2.verifyHex(data, sign2)); } @Test @@ -224,7 +256,6 @@ public class SM2Test { final SM2 sm2 = new SM2(ecPrivateKeyParameters, ecPublicKeyParameters); sm2.setMode(SM2Engine.Mode.C1C2C3); final String encryptHex = sm2.encryptHex(data, KeyType.PublicKey); - Console.log(encryptHex); final String decryptStr = sm2.decryptStr(encryptHex, KeyType.PrivateKey); Assert.assertEquals(data, decryptStr);