From 9281b0f8e67b1285b4d92955e49e22e5c65b2050 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 9 Sep 2024 01:26:00 +0800 Subject: [PATCH] add ECKeySpecUtil --- .../org/dromara/hutool/crypto/KeyUtil.java | 4 +- .../asymmetric/AsymmetricDecryptor.java | 2 +- .../dromara/hutool/crypto/asymmetric/SM2.java | 265 +++++++++++++++--- .../hutool/crypto/bc/ECKeySpecUtil.java | 127 +++++++++ .../dromara/hutool/crypto/bc/ECKeyUtil.java | 197 +++++++------ .../org/dromara/hutool/crypto/bc/PemUtil.java | 8 +- .../dromara/hutool/crypto/bc/SM2Constant.java | 47 ++++ .../org/dromara/hutool/crypto/bc/SmUtil.java | 22 +- .../crypto/asymmetric/Issue3728Test.java | 11 +- .../hutool/crypto/asymmetric/SM2Test.java | 9 +- .../hutool/crypto/bc/ECKeyUtilTest.java | 12 + 11 files changed, 551 insertions(+), 153 deletions(-) create mode 100644 hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeySpecUtil.java create mode 100644 hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/SM2Constant.java diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyUtil.java index 82a721d2b..168f4d68a 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyUtil.java @@ -25,7 +25,7 @@ import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.util.RandomUtil; import org.dromara.hutool.crypto.asymmetric.AsymmetricAlgorithm; import org.dromara.hutool.crypto.bc.ECKeyUtil; -import org.dromara.hutool.crypto.bc.SmUtil; +import org.dromara.hutool.crypto.bc.SM2Constant; import org.dromara.hutool.crypto.provider.GlobalProviderFactory; import org.dromara.hutool.crypto.symmetric.SymmetricAlgorithm; @@ -421,7 +421,7 @@ public class KeyUtil { public static KeyPair generateKeyPair(final String algorithm, final int keySize, final byte[] seed) { // SM2算法需要单独定义其曲线生成 if ("SM2".equalsIgnoreCase(algorithm)) { - final ECGenParameterSpec sm2p256v1 = new ECGenParameterSpec(SmUtil.SM2_CURVE_NAME); + final ECGenParameterSpec sm2p256v1 = new ECGenParameterSpec(SM2Constant.SM2_CURVE_NAME); return generateKeyPair(algorithm, keySize, seed, sm2p256v1); } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricDecryptor.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricDecryptor.java index 56bf9803f..0626b4dbf 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricDecryptor.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricDecryptor.java @@ -89,7 +89,7 @@ public interface AsymmetricDecryptor { * * @param data 数据,Hex(16进制)或Base64字符串 * @param keyType 密钥类型 - * @return 解密后的密文 + * @return 解密后的密文,UTF-8编码 * @since 4.5.2 */ default String decryptStr(final String data, final KeyType keyType) { diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/SM2.java index cbc897066..c6322206b 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/SM2.java @@ -33,13 +33,17 @@ import org.bouncycastle.util.BigIntegers; import org.bouncycastle.util.encoders.Hex; import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.codec.binary.HexUtil; +import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.crypto.CryptoException; import org.dromara.hutool.crypto.SecureUtil; import org.dromara.hutool.crypto.bc.ECKeyUtil; import org.dromara.hutool.crypto.bc.SmUtil; +import java.io.InputStream; import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; @@ -73,19 +77,24 @@ public class SM2 extends AbstractAsymmetricCrypto { private ECPrivateKeyParameters privateKeyParams; private ECPublicKeyParameters publicKeyParams; + /** * 自定义随机数 */ private SecureRandom random; + /** + * 是否去除压缩04压缩标识 + */ + private boolean removeCompressedFlag; private DSAEncoding encoding = StandardDSAEncoding.INSTANCE; private Digest digest = new SM3Digest(); private SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2; - // ------------------------------------------------------------------ Constructor start + // region ----- Constructors /** - * 构造,生成新的私钥公钥对 + * 构造,生成新的随机私钥公钥对 */ public SM2() { this(null, (byte[]) null); @@ -113,29 +122,11 @@ public class SM2 extends AbstractAsymmetricCrypto { */ public SM2(final byte[] privateKey, final byte[] publicKey) { this( - ECKeyUtil.decodePrivateKeyParams(privateKey), - ECKeyUtil.decodePublicKeyParams(publicKey) + ECKeyUtil.generateSm2PrivateKey(privateKey), + ECKeyUtil.generateSm2PublicKey(publicKey) ); } - /** - * 构造
- * 私钥和公钥同时为空时生成一对新的私钥和公钥
- * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * - * @param privateKey 私钥 - * @param publicKey 公钥 - */ - public SM2(final PrivateKey privateKey, final PublicKey publicKey) { - this(ECKeyUtil.toPrivateParams(privateKey), ECKeyUtil.toPublicParams(publicKey)); - if (null != privateKey) { - this.privateKey = privateKey; - } - if (null != publicKey) { - this.publicKey = publicKey; - } - } - /** * 构造
* 私钥和公钥同时为空时生成一对新的私钥和公钥
@@ -147,7 +138,11 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.2.0 */ public SM2(final String privateKeyDValue, final String publicKeyPointXHex, final String publicKeyPointYHex) { - this(ECKeyUtil.toSm2PrivateParams(privateKeyDValue), ECKeyUtil.toSm2PublicParams(publicKeyPointXHex, publicKeyPointYHex)); + this( + SecureUtil.decode(privateKeyDValue), + SecureUtil.decode(publicKeyPointXHex), + SecureUtil.decode(publicKeyPointYHex) + ); } /** @@ -161,8 +156,23 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.2.0 */ public SM2(final byte[] privateKeyDValue, final byte[] publicKeyPointX, final byte[] publicKeyPointY) { - this(ECKeyUtil.toSm2PrivateParams(privateKeyDValue), - ECKeyUtil.toSm2PublicParams(publicKeyPointX, publicKeyPointY)); + this(ECKeyUtil.generateSm2PrivateKey(privateKeyDValue), + ECKeyUtil.generateSm2PublicKey(publicKeyPointX, publicKeyPointY)); + } + + /** + * 构造
+ * 私钥和公钥同时为空时生成一对新的私钥和公钥
+ * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 + * + * @param privateKey 私钥 + * @param publicKey 公钥 + */ + public SM2(final PrivateKey privateKey, final PublicKey publicKey) { + super(ALGORITHM_SM2, new KeyPair(publicKey, privateKey)); + this.privateKeyParams = ECKeyUtil.toPrivateParams(this.privateKey); + this.publicKeyParams = ECKeyUtil.toPublicParams(this.publicKey); + this.init(); } /** @@ -179,8 +189,7 @@ public class SM2 extends AbstractAsymmetricCrypto { this.publicKeyParams = publicKeyParams; this.init(); } - - // ------------------------------------------------------------------ Constructor end + // endregion /** * 初始化
@@ -191,6 +200,7 @@ public class SM2 extends AbstractAsymmetricCrypto { */ public SM2 init() { if (null == this.privateKeyParams && null == this.publicKeyParams) { + // 随机密钥对 super.initKeys(); this.privateKeyParams = ECKeyUtil.toPrivateParams(this.privateKey); this.publicKeyParams = ECKeyUtil.toPublicParams(this.publicKey); @@ -205,7 +215,91 @@ public class SM2 extends AbstractAsymmetricCrypto { return this; } - // --------------------------------------------------------------------------------- Encrypt + // region ----- Encrypt + + /** + * 使用公钥加密,SM2非对称加密的结果由C1,C3,C2三部分组成,其中: + * + *
+	 * C1 生成随机数的计算出的椭圆曲线点
+	 * C3 SM3的摘要值
+	 * C2 密文数据
+	 * 
+ * + * @param data 被加密的字符串,UTF8编码 + * @return 加密后的Base64 + * @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常 + */ + public String encryptBase64(final String data) { + return encryptBase64(data, KeyType.PublicKey); + } + + /** + * 使用公钥加密,SM2非对称加密的结果由C1,C3,C2三部分组成,其中: + * + *
+	 * C1 生成随机数的计算出的椭圆曲线点
+	 * C3 SM3的摘要值
+	 * C2 密文数据
+	 * 
+ * + * @param in 被加密的数据流 + * @return 加密后的Base64 + * @throws IORuntimeException IO异常 + */ + public String encryptBase64(final InputStream in) throws IORuntimeException { + return encryptBase64(in, KeyType.PublicKey); + } + + /** + * 使用公钥加密,SM2非对称加密的结果由C1,C3,C2三部分组成,其中: + * + *
+	 * C1 生成随机数的计算出的椭圆曲线点
+	 * C3 SM3的摘要值
+	 * C2 密文数据
+	 * 
+ * + * @param data 被加密的bytes + * @return 加密后的Base64 + */ + public String encryptBase64(final byte[] data) { + return encryptBase64(data, KeyType.PublicKey); + } + + /** + * 使用公钥加密,SM2非对称加密的结果由C1,C3,C2三部分组成,其中: + * + *
+	 * C1 生成随机数的计算出的椭圆曲线点
+	 * C3 SM3的摘要值
+	 * C2 密文数据
+	 * 
+ * + * @param data 被加密的字符串,UTF8编码 + * @return 加密后的bytes + * @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常 + */ + public byte[] encrypt(final String data) { + return encrypt(data, KeyType.PublicKey); + } + + /** + * 使用公钥加密,SM2非对称加密的结果由C1,C3,C2三部分组成,其中: + * + *
+	 * C1 生成随机数的计算出的椭圆曲线点
+	 * C3 SM3的摘要值
+	 * C2 密文数据
+	 * 
+ * + * @param in 被加密的数据流 + * @return 加密后的bytes + * @throws IORuntimeException IO异常 + */ + public byte[] encrypt(final InputStream in) throws IORuntimeException { + return encrypt(in, KeyType.PublicKey); + } /** * 使用公钥加密,SM2非对称加密的结果由C1,C3,C2三部分组成,其中: @@ -267,15 +361,60 @@ public class SM2 extends AbstractAsymmetricCrypto { final SM2Engine engine = getEngine(); try { engine.init(true, pubKeyParameters); - return engine.processBlock(data, 0, data.length); + final byte[] result = engine.processBlock(data, 0, data.length); + return this.removeCompressedFlag ? removeCompressedFlag(result) : result; } catch (final InvalidCipherTextException e) { throw new CryptoException(e); } finally { lock.unlock(); } } + // endregion - // --------------------------------------------------------------------------------- Decrypt + // region ----- Decrypt + + /** + * 使用私钥解密 + * + * @param data SM2密文数据,Hex(16进制)或Base64字符串 + * @return 解密后的字符串,UTF-8 编码 + */ + public String decryptStr(final String data) { + return decryptStr(data, KeyType.PrivateKey); + } + + /** + * 使用私钥解密 + * + * @param data SM2密文数据,Hex(16进制)或Base64字符串 + * @param charset 编码 + * @return 解密后的bytes + * @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常 + */ + public String decryptStr(final String data, final Charset charset) { + return decryptStr(data, KeyType.PrivateKey, charset); + } + + /** + * 使用私钥解密 + * + * @param in 密文数据流 + * @return 解密后的bytes + * @throws IORuntimeException IO异常 + */ + public byte[] decrypt(final InputStream in) throws IORuntimeException { + return super.decrypt(in, KeyType.PrivateKey); + } + + /** + * 使用私钥解密 + * + * @param data SM2密文,实际包含三部分:ECC公钥、真正的密文、公钥和原文的SM3-HASH值 + * @return 解密后的bytes + */ + public byte[] decrypt(final String data) { + return super.decrypt(data, KeyType.PrivateKey); + } /** * 使用私钥解密 @@ -316,17 +455,7 @@ public class SM2 extends AbstractAsymmetricCrypto { */ public byte[] decrypt(byte[] data, final CipherParameters privateKeyParameters) throws CryptoException { Assert.isTrue(data.length > 1, "Invalid SM2 cipher text, must be at least 1 byte long"); - // 检查数据,gmssl等库生成的密文不包含04前缀(非压缩数据标识),此处检查并补充 - // 参考:https://blog.csdn.net/softt/article/details/139978608 - // 根据公钥压缩形态不同,密文分为两种压缩形式: - // C1( 03 + X ) + C3(32个字节)+ C2 - // C1( 02 + X ) + C3(32个字节)+ C2 - // 非压缩公钥正常形态为04 + X + Y,由于各个算法库差异,04有时候会省略 - // 非压缩密文正常形态为04 + C1 + C3 + C2 - if (data[0] != 0x04 && data[0] != 0x02 && data[0] != 0x03) { - // 默认非压缩形态 - data = ArrayUtil.insert(data, 0, 0x04); - } + data = prependCompressedFlag(data); lock.lock(); final SM2Engine engine = getEngine(); @@ -339,7 +468,9 @@ public class SM2 extends AbstractAsymmetricCrypto { lock.unlock(); } } - // --------------------------------------------------------------------------------- Sign and Verify + //endregion + + // region ----- Sign and Verify /** * 用私钥对信息生成数字签名 @@ -458,6 +589,7 @@ public class SM2 extends AbstractAsymmetricCrypto { lock.unlock(); } } + // endregion @Override public SM2 setPrivateKey(final PrivateKey privateKey) { @@ -513,6 +645,18 @@ public class SM2 extends AbstractAsymmetricCrypto { return this; } + /** + * 设置是否移除压缩标记,默认为false
+ * 移除后的密文兼容gmssl等库 + * + * @param removeCompressedFlag 是否移除压缩标记 + * @return this + */ + public SM2 setRemoveCompressedFlag(final boolean removeCompressedFlag) { + this.removeCompressedFlag = removeCompressedFlag; + return this; + } + /** * 设置DSA signatures的编码为PlainDSAEncoding * @@ -651,5 +795,42 @@ public class SM2 extends AbstractAsymmetricCrypto { this.digest.reset(); return this.signer; } + + /** + * 去除04压缩标识
+ * gmssl等库生成的密文不包含04前缀,此处兼容 + * + * @param data 密文数据 + * @return 处理后的数据 + */ + private static byte[] removeCompressedFlag(final byte[] data) { + if (data[0] != 0x04) { + return data; + } + final byte[] result = new byte[data.length - 1]; + System.arraycopy(data, 1, result, 0, result.length); + return result; + } + + /** + * 追加压缩标识
+ * 检查数据,gmssl等库生成的密文不包含04前缀(非压缩数据标识),此处检查并补充 + * 参考:https://blog.csdn.net/softt/article/details/139978608 + * 根据公钥压缩形态不同,密文分为两种压缩形式: + * C1( 03 + X ) + C3(32个字节)+ C2 + * C1( 02 + X ) + C3(32个字节)+ C2 + * 非压缩公钥正常形态为04 + X + Y,由于各个算法库差异,04有时候会省略 + * 非压缩密文正常形态为04 + C1 + C3 + C2 + * + * @param data 待解密数据 + * @return 增加压缩标识后的数据 + */ + private static byte[] prependCompressedFlag(byte[] data) { + if (data[0] != 0x04 && data[0] != 0x02 && data[0] != 0x03) { + // 默认非压缩形态 + data = ArrayUtil.insert(data, 0, 0x04); + } + return data; + } // ------------------------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeySpecUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeySpecUtil.java new file mode 100644 index 000000000..67d70a3f6 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeySpecUtil.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.crypto.bc; + +import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; +import org.bouncycastle.jcajce.spec.OpenSSHPublicKeySpec; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPrivateKeySpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; + +/** + * BC密钥规范工具类 + * + * @author Looly + * @since 6.0.0 + */ +public class ECKeySpecUtil { + + /** + * 获取私钥规范 + * + * @param d 私钥D值 + * @param parameterSpec {@link ECParameterSpec} + * @return ECPrivateKeySpec + */ + public static ECPrivateKeySpec getPrivateKeySpec(final byte[] d, final ECParameterSpec parameterSpec) { + return getPrivateKeySpec(BigIntegers.fromUnsignedByteArray(d), parameterSpec); + } + + /** + * 获取私钥规范 + * + * @param d 私钥D值 + * @param parameterSpec {@link ECParameterSpec} + * @return ECPrivateKeySpec + */ + public static ECPrivateKeySpec getPrivateKeySpec(final BigInteger d, final ECParameterSpec parameterSpec) { + return new ECPrivateKeySpec(d, parameterSpec); + } + + /** + * 获取公钥规范 + * + * @param q 公钥Q值 + * @param parameterSpec {@link ECParameterSpec} + * @return ECPublicKeySpec + */ + public static ECPublicKeySpec getPublicKeySpec(final byte[] q, final ECParameterSpec parameterSpec) { + return getPublicKeySpec(parameterSpec.getCurve().decodePoint(q), parameterSpec); + } + + /** + * 获取公钥规范 + * + * @param x 公钥x坐标 + * @param y 公钥y坐标 + * @param parameterSpec {@link ECParameterSpec} + * @return ECPublicKeySpec + */ + public static ECPublicKeySpec getPublicKeySpec(final byte[] x, final byte[] y, final ECParameterSpec parameterSpec) { + return getPublicKeySpec( + BigIntegers.fromUnsignedByteArray(x), + BigIntegers.fromUnsignedByteArray(y), + parameterSpec); + } + + /** + * 获取公钥规范 + * + * @param x 公钥x坐标 + * @param y 公钥y坐标 + * @param parameterSpec {@link ECParameterSpec} + * @return ECPublicKeySpec + */ + public static ECPublicKeySpec getPublicKeySpec(final BigInteger x, final BigInteger y, final ECParameterSpec parameterSpec) { + return getPublicKeySpec(parameterSpec.getCurve().createPoint(x, y), parameterSpec); + } + + /** + * 获取公钥规范 + * + * @param ecPoint 公钥坐标 + * @param parameterSpec {@link ECParameterSpec} + * @return ECPublicKeySpec + */ + public static ECPublicKeySpec getPublicKeySpec(final ECPoint ecPoint, final ECParameterSpec parameterSpec) { + return new ECPublicKeySpec(ecPoint, parameterSpec); + } + + /** + * 创建{@link OpenSSHPrivateKeySpec} + * + * @param key 私钥,需为PKCS#1格式或OpenSSH格式 + * @return {@link OpenSSHPrivateKeySpec} + */ + public static OpenSSHPrivateKeySpec getOpenSSHPrivateKeySpec(final byte[] key) { + return new OpenSSHPrivateKeySpec(key); + } + + /** + * 创建{@link OpenSSHPublicKeySpec} + * + * @param key 公钥,需为PKCS#1格式 + * @return {@link OpenSSHPublicKeySpec} + */ + public static OpenSSHPublicKeySpec getOpenSSHPublicKeySpec(final byte[] key) { + return new OpenSSHPublicKeySpec(key); + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeyUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeyUtil.java index 2e28034c1..3fa667f05 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeyUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeyUtil.java @@ -16,27 +16,26 @@ package org.dromara.hutool.crypto.bc; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; -import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; -import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util; -import org.bouncycastle.jce.spec.ECNamedCurveSpec; -import org.dromara.hutool.core.io.IORuntimeException; +import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.sec.ECPrivateKey; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util; import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; -import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; -import org.bouncycastle.jcajce.spec.OpenSSHPublicKeySpec; +import org.bouncycastle.jce.interfaces.ECPrivateKey; +import org.bouncycastle.jce.spec.ECNamedCurveSpec; +import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.FixedPointCombMultiplier; import org.bouncycastle.util.BigIntegers; +import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.crypto.CryptoException; import org.dromara.hutool.crypto.KeyUtil; import org.dromara.hutool.crypto.SecureUtil; @@ -49,6 +48,8 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.ECPublicKeySpec; import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.util.Objects; /** @@ -59,15 +60,54 @@ import java.util.Objects; */ public class ECKeyUtil { + /** + * 根据私钥参数获取公钥参数 + * + * @param privateKeyParameters 私钥参数 + * @return 公钥参数 + * @since 5.5.9 + */ + public static ECPublicKeyParameters getPublicParams(final ECPrivateKeyParameters privateKeyParameters) { + final ECDomainParameters domainParameters = privateKeyParameters.getParameters(); + final ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), privateKeyParameters.getD()); + return new ECPublicKeyParameters(q, domainParameters); + } + + /** + * 根据私钥获取EC公钥 + * + * @param privateKey EC私钥 + * @param spec 密钥规范 + * @return EC公钥 + */ + public static PublicKey getECPublicKey(final ECPrivateKey privateKey, final ECParameterSpec spec) { + final org.bouncycastle.jce.spec.ECPublicKeySpec keySpec = + new org.bouncycastle.jce.spec.ECPublicKeySpec(getQFromD(privateKey.getD(), spec), spec); + return KeyUtil.generatePublicKey("EC", keySpec); + } + + /** + * 根据私钥D值获取公钥的点坐标(Q值) + * + * @param d 私钥d值 + * @param spec 密钥规范 + * @return 公钥的点坐标 + */ + public static ECPoint getQFromD(final BigInteger d, final ECParameterSpec spec) { + return spec.getG().multiply(d).normalize(); + } + + // region ----- encode and decode + /** * 只获取私钥里的d,32位字节 * - * @param privateKey {@link PublicKey},必须为org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey + * @param privateKey {@link PublicKey},必须为org.bouncycastle.jce.interfaces.ECPrivateKey * @return 压缩得到的X * @since 5.1.6 */ public static byte[] encodeECPrivateKey(final PrivateKey privateKey) { - return ((BCECPrivateKey) privateKey).getD().toByteArray(); + return ((ECPrivateKey) privateKey).getD().toByteArray(); } /** @@ -86,7 +126,7 @@ public class ECKeyUtil { * 编码压缩EC公钥(基于BouncyCastle),即Q值
* 见:https://www.cnblogs.com/xinzhao/p/8963724.html * - * @param publicKey {@link PublicKey},必须为org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey + * @param publicKey {@link PublicKey},必须为org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey * @param isCompressed 是否压缩 * @return 得到的Q * @since 5.5.9 @@ -112,7 +152,7 @@ public class ECKeyUtil { * 解码恢复EC压缩公钥,支持Base64和Hex编码,(基于BouncyCastle) * * @param encodeByte 压缩公钥 - * @param curveName EC曲线名,例如{@link SmUtil#SM2_DOMAIN_PARAMS} + * @param curveName EC曲线名,例如{@link SM2Constant#SM2_DOMAIN_PARAMS} * @return 公钥 * @since 4.4.4 */ @@ -125,6 +165,7 @@ public class ECKeyUtil { final ECNamedCurveSpec ecSpec = new ECNamedCurveSpec(curveName, curve, x9ECParameters.getG(), x9ECParameters.getN()); return KeyUtil.generatePublicKey("EC", new ECPublicKeySpec(point, ecSpec)); } + // endregion /** * 密钥转换为AsymmetricKeyParameter @@ -142,20 +183,7 @@ public class ECKeyUtil { return null; } - /** - * 根据私钥参数获取公钥参数 - * - * @param privateKeyParameters 私钥参数 - * @return 公钥参数 - * @since 5.5.9 - */ - public static ECPublicKeyParameters getPublicParams(final ECPrivateKeyParameters privateKeyParameters) { - final ECDomainParameters domainParameters = privateKeyParameters.getParameters(); - final ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), privateKeyParameters.getD()); - return new ECPublicKeyParameters(q, domainParameters); - } - - //--------------------------------------------------------------------------- Public Key + // region ----- toXXPublicParams /** * 转换为 ECPublicKeyParameters @@ -164,7 +192,7 @@ public class ECKeyUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toSm2PublicParams(final byte[] q) { - return toPublicParams(q, SmUtil.SM2_DOMAIN_PARAMS); + return toPublicParams(q, SM2Constant.SM2_DOMAIN_PARAMS); } /** @@ -174,7 +202,7 @@ public class ECKeyUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toSm2PublicParams(final String q) { - return toPublicParams(q, SmUtil.SM2_DOMAIN_PARAMS); + return toPublicParams(q, SM2Constant.SM2_DOMAIN_PARAMS); } /** @@ -185,7 +213,7 @@ public class ECKeyUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toSm2PublicParams(final String x, final String y) { - return toPublicParams(x, y, SmUtil.SM2_DOMAIN_PARAMS); + return toPublicParams(x, y, SM2Constant.SM2_DOMAIN_PARAMS); } /** @@ -196,7 +224,7 @@ public class ECKeyUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toSm2PublicParams(final byte[] xBytes, final byte[] yBytes) { - return toPublicParams(xBytes, yBytes, SmUtil.SM2_DOMAIN_PARAMS); + return toPublicParams(xBytes, yBytes, SM2Constant.SM2_DOMAIN_PARAMS); } /** @@ -296,8 +324,10 @@ public class ECKeyUtil { throw new CryptoException(e); } } + // endreion - //--------------------------------------------------------------------------- Private Key + + // region ----- toXXPrivateParams /** * 转换为 ECPrivateKeyParameters @@ -306,7 +336,7 @@ public class ECKeyUtil { * @return ECPrivateKeyParameters */ public static ECPrivateKeyParameters toSm2PrivateParams(final String d) { - return toPrivateParams(d, SmUtil.SM2_DOMAIN_PARAMS); + return toPrivateParams(d, SM2Constant.SM2_DOMAIN_PARAMS); } /** @@ -316,7 +346,7 @@ public class ECKeyUtil { * @return ECPrivateKeyParameters */ public static ECPrivateKeyParameters toSm2PrivateParams(final byte[] d) { - return toPrivateParams(d, SmUtil.SM2_DOMAIN_PARAMS); + return toPrivateParams(d, SM2Constant.SM2_DOMAIN_PARAMS); } /** @@ -326,7 +356,7 @@ public class ECKeyUtil { * @return ECPrivateKeyParameters */ public static ECPrivateKeyParameters toSm2PrivateParams(final BigInteger d) { - return toPrivateParams(d, SmUtil.SM2_DOMAIN_PARAMS); + return toPrivateParams(d, SM2Constant.SM2_DOMAIN_PARAMS); } /** @@ -389,17 +419,20 @@ public class ECKeyUtil { throw new CryptoException(e); } } + // endregion + + // region ----- 生成密钥 generateXXKey /** - * 将SM2算法的{@link ECPrivateKey} 转换为 {@link PrivateKey} + * 将SM2算法的{@link ASN1Encodable}格式私钥 生成为 {@link PrivateKey} * - * @param privateKey {@link ECPrivateKey} + * @param privateKey {@link ASN1Encodable}格式的私钥 * @return {@link PrivateKey} */ - public static PrivateKey toSm2PrivateKey(final ECPrivateKey privateKey) { + public static PrivateKey generatePrivateKey(final ASN1Encodable privateKey) { try { final PrivateKeyInfo info = new PrivateKeyInfo( - new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SmUtil.ID_SM2_PUBLIC_KEY_PARAM), privateKey); + new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SM2Constant.ID_SM2_PUBLIC_KEY_PARAM), privateKey); return KeyUtil.generatePrivateKey("SM2", info.getEncoded()); } catch (final IOException e) { throw new IORuntimeException(e); @@ -407,65 +440,45 @@ public class ECKeyUtil { } /** - * 创建{@link OpenSSHPrivateKeySpec} - * - * @param key 私钥,需为PKCS#1格式 - * @return {@link OpenSSHPrivateKeySpec} - * @since 5.5.9 - */ - public static KeySpec createOpenSSHPrivateKeySpec(final byte[] key) { - return new OpenSSHPrivateKeySpec(key); - } - - /** - * 创建{@link OpenSSHPublicKeySpec} - * - * @param key 公钥,需为PKCS#1格式 - * @return {@link OpenSSHPublicKeySpec} - * @since 5.5.9 - */ - public static KeySpec createOpenSSHPublicKeySpec(final byte[] key) { - return new OpenSSHPublicKeySpec(key); - } - - /** - * 尝试解析转换各种类型私钥为{@link ECPrivateKeyParameters},支持包括: + * 生成SM2私钥,支持包括: * *
    *
  • D值
  • *
  • PKCS#8
  • *
  • PKCS#1
  • + *
  • OpenSSH格式
  • *
* * @param privateKeyBytes 私钥 * @return {@link ECPrivateKeyParameters} - * @since 5.5.9 */ - public static ECPrivateKeyParameters decodePrivateKeyParams(final byte[] privateKeyBytes) { + public static PrivateKey generateSm2PrivateKey(final byte[] privateKeyBytes) { if (null == privateKeyBytes) { return null; } + final String algorithm = "SM2"; + KeySpec keySpec; + // 尝试D值 try { - // 尝试D值 - return toSm2PrivateParams(privateKeyBytes); + keySpec = ECKeySpecUtil.getPrivateKeySpec(privateKeyBytes, SM2Constant.SM2_EC_SPEC); + return KeyUtil.generatePrivateKey(algorithm, keySpec); } catch (final Exception ignore) { - // ignore } - PrivateKey privateKey; //尝试PKCS#8 try { - privateKey = KeyUtil.generatePrivateKey("sm2", privateKeyBytes); + keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); + return KeyUtil.generatePrivateKey(algorithm, keySpec); } catch (final Exception ignore) { - // 尝试PKCS#1 - privateKey = KeyUtil.generatePrivateKey("sm2", createOpenSSHPrivateKeySpec(privateKeyBytes)); } - return toPrivateParams(privateKey); + // 尝试PKCS#1或OpenSSH格式 + keySpec = ECKeySpecUtil.getOpenSSHPrivateKeySpec(privateKeyBytes); + return KeyUtil.generatePrivateKey(algorithm, keySpec); } /** - * 尝试解析转换各种类型公钥为{@link ECPublicKeyParameters},支持包括: + * 生成SM2公钥,支持包括: * *
    *
  • Q值
  • @@ -475,28 +488,46 @@ public class ECKeyUtil { * * @param publicKeyBytes 公钥 * @return {@link ECPublicKeyParameters} - * @since 5.5.9 */ - public static ECPublicKeyParameters decodePublicKeyParams(final byte[] publicKeyBytes) { - if(null == publicKeyBytes){ + public static PublicKey generateSm2PublicKey(final byte[] publicKeyBytes) { + if (null == publicKeyBytes) { return null; } + final String algorithm = "SM2"; + KeySpec keySpec; + // 尝试Q值 try { - // 尝试Q值 - return toSm2PublicParams(publicKeyBytes); + keySpec = ECKeySpecUtil.getPublicKeySpec(publicKeyBytes, SM2Constant.SM2_EC_SPEC); + return KeyUtil.generatePublicKey(algorithm, keySpec); } catch (final Exception ignore) { // ignore } - PublicKey publicKey; //尝试X.509 try { - publicKey = KeyUtil.generatePublicKey("sm2", publicKeyBytes); + keySpec = new X509EncodedKeySpec(publicKeyBytes); + return KeyUtil.generatePublicKey(algorithm, keySpec); } catch (final Exception ignore) { - // 尝试PKCS#1 - publicKey = KeyUtil.generatePublicKey("sm2", createOpenSSHPublicKeySpec(publicKeyBytes)); } - return toPublicParams(publicKey); + // 尝试PKCS#1 + keySpec = ECKeySpecUtil.getOpenSSHPublicKeySpec(publicKeyBytes); + return KeyUtil.generatePublicKey(algorithm, keySpec); } + + /** + * 尝试解析转换各种类型公钥为{@link ECPublicKeyParameters},支持包括: + * + * @param x 坐标X + * @param y 坐标y + * @return {@link ECPublicKeyParameters} + */ + public static PublicKey generateSm2PublicKey(final byte[] x, final byte[] y) { + if (null == x || null == y) { + return null; + } + return KeyUtil.generatePublicKey("sm2", + ECKeySpecUtil.getPublicKeySpec(x, y, SM2Constant.SM2_EC_SPEC)); + } + // endregion } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/PemUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/PemUtil.java index 40be52221..8a1222e70 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/PemUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/PemUtil.java @@ -43,6 +43,7 @@ import java.security.PublicKey; */ public class PemUtil { + // region ----- readPem /** * 读取PEM格式的私钥 * @@ -84,7 +85,7 @@ public class PemUtil { return KeyUtil.generatePrivateKey("EC", object.getContent()); } catch (final Exception e) { // 尝试PKCS#1 - return KeyUtil.generatePrivateKey("EC", ECKeyUtil.createOpenSSHPrivateKeySpec(object.getContent())); + return KeyUtil.generatePrivateKey("EC", ECKeySpecUtil.getOpenSSHPrivateKeySpec(object.getContent())); } } if (type.endsWith("PRIVATE KEY")) { @@ -98,7 +99,7 @@ public class PemUtil { return KeyUtil.generatePublicKey("EC", object.getContent()); } catch (final Exception ignore) { // 尝试PKCS#1 - return KeyUtil.generatePublicKey("EC", ECKeyUtil.createOpenSSHPublicKeySpec(object.getContent())); + return KeyUtil.generatePublicKey("EC", ECKeySpecUtil.getOpenSSHPublicKeySpec(object.getContent())); } } else if (type.endsWith("PUBLIC KEY")) { return KeyUtil.generateRSAPublicKey(object.getContent()); @@ -155,7 +156,9 @@ public class PemUtil { IoUtil.closeQuietly(pemReader); } } + // endregion + // region ----- writePem /** * 将私钥或公钥转换为PEM格式的字符串 * @param type 密钥类型(私钥、公钥、证书) @@ -221,4 +224,5 @@ public class PemUtil { IoUtil.closeQuietly(pemWriter); } } + // endregion } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/SM2Constant.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/SM2Constant.java new file mode 100644 index 000000000..d693a05cd --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/SM2Constant.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.crypto.bc; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.spec.ECParameterSpec; + +/** + * SM2常量 + * + * @author Looly + * @since 6.0.0 + */ +public class SM2Constant { + /** + * SM2默认曲线 + */ + public static final String SM2_CURVE_NAME = "sm2p256v1"; + /** + * SM2椭圆曲线参数类 + */ + public static final ECParameterSpec SM2_EC_SPEC = ECNamedCurveTable.getParameterSpec(SM2_CURVE_NAME); + /** + * SM2推荐曲线参数(来自https://github.com/ZZMarquis/gmhelper) + */ + public static final ECDomainParameters SM2_DOMAIN_PARAMS = BCUtil.toDomainParams(SM2_EC_SPEC); + /** + * SM2国密算法公钥参数的Oid标识 + */ + public static final ASN1ObjectIdentifier ID_SM2_PUBLIC_KEY_PARAM = new ASN1ObjectIdentifier("1.2.156.10197.1.301"); +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/SmUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/SmUtil.java index d718d735d..348e8e939 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/SmUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/SmUtil.java @@ -16,8 +16,6 @@ package org.dromara.hutool.crypto.bc; -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; @@ -29,10 +27,10 @@ import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.crypto.CryptoException; import org.dromara.hutool.crypto.asymmetric.SM2; -import org.dromara.hutool.crypto.digest.mac.HMac; -import org.dromara.hutool.crypto.digest.mac.HmacAlgorithm; import org.dromara.hutool.crypto.digest.SM3; import org.dromara.hutool.crypto.digest.mac.BCHMacEngine; +import org.dromara.hutool.crypto.digest.mac.HMac; +import org.dromara.hutool.crypto.digest.mac.HmacAlgorithm; import org.dromara.hutool.crypto.digest.mac.MacEngine; import org.dromara.hutool.crypto.symmetric.SM4; import org.dromara.hutool.crypto.symmetric.SymmetricCrypto; @@ -64,18 +62,6 @@ import java.security.PublicKey; public class SmUtil { private final static int RS_LEN = 32; - /** - * SM2默认曲线 - */ - public static final String SM2_CURVE_NAME = "sm2p256v1"; - /** - * SM2推荐曲线参数(来自https://github.com/ZZMarquis/gmhelper) - */ - public static final ECDomainParameters SM2_DOMAIN_PARAMS = BCUtil.toDomainParams(GMNamedCurves.getByName(SM2_CURVE_NAME)); - /** - * SM2国密算法公钥参数的Oid标识 - */ - public static final ASN1ObjectIdentifier ID_SM2_PUBLIC_KEY_PARAM = new ASN1ObjectIdentifier("1.2.156.10197.1.301"); /** * 创建SM2算法对象
    @@ -271,7 +257,7 @@ public class SmUtil { public static byte[] rsAsn1ToPlain(final byte[] rsDer) { final BigInteger[] decode; try { - decode = StandardDSAEncoding.INSTANCE.decode(SM2_DOMAIN_PARAMS.getN(), rsDer); + decode = StandardDSAEncoding.INSTANCE.decode(SM2Constant.SM2_DOMAIN_PARAMS.getN(), rsDer); } catch (final IOException e) { throw new IORuntimeException(e); } @@ -296,7 +282,7 @@ public class SmUtil { final BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN)); final BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2)); try { - return StandardDSAEncoding.INSTANCE.encode(SM2_DOMAIN_PARAMS.getN(), r, s); + return StandardDSAEncoding.INSTANCE.encode(SM2Constant.SM2_DOMAIN_PARAMS.getN(), r, s); } catch (final IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/Issue3728Test.java b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/Issue3728Test.java index a2294d0c0..d3ca1e63e 100644 --- a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/Issue3728Test.java +++ b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/Issue3728Test.java @@ -16,13 +16,20 @@ package org.dromara.hutool.crypto.asymmetric; +import org.dromara.hutool.core.text.StrUtil; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class Issue3728Test { @Test void sm2Test() { - String publicKey="04beab9c2b800c03263b2d9cfcc832eb6827d5b62dc2ec7f8503c8832799af13b057d6b5bf5bc6c144753f3aa8b6cef8acb00a379a4fbed2f90c546fc2b4586bb0"; - String privateKey="3920cfc4828339b34da62b97b44d49d3a9c7dc84d9e6732d4b18f681a339519c"; + final String publicKey="04beab9c2b800c03263b2d9cfcc832eb6827d5b62dc2ec7f8503c8832799af13b057d6b5bf5bc6c144753f3aa8b6cef8acb00a379a4fbed2f90c546fc2b4586bb0"; + final String privateKey="3920cfc4828339b34da62b97b44d49d3a9c7dc84d9e6732d4b18f681a339519c"; final SM2 sm2 = new SM2(privateKey, publicKey); + + final String data = "你好 hutool"; + final byte[] encrypt = sm2.encrypt(data); + final byte[] decrypt = sm2.decrypt(encrypt); + Assertions.assertEquals(data, StrUtil.utf8Str(decrypt)); } } diff --git a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/SM2Test.java b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/SM2Test.java index 5fab7587b..0d86267e4 100644 --- a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/SM2Test.java +++ b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/SM2Test.java @@ -24,6 +24,7 @@ import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.crypto.bc.ECKeyUtil; import org.dromara.hutool.crypto.KeyUtil; import org.dromara.hutool.crypto.SecureUtil; +import org.dromara.hutool.crypto.bc.SM2Constant; import org.dromara.hutool.crypto.bc.SmUtil; import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; @@ -35,6 +36,7 @@ import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; +import java.util.Objects; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -208,8 +210,8 @@ public class SM2Test { final byte[] data = KeyUtil.encodeECPublicKey(publicKey); final String encodeHex = HexUtil.encodeStr(data); final String encodeB64 = Base64.encode(data); - final PublicKey Hexdecode = KeyUtil.decodeECPoint(encodeHex, SmUtil.SM2_CURVE_NAME); - final PublicKey B64decode = KeyUtil.decodeECPoint(encodeB64, SmUtil.SM2_CURVE_NAME); + final PublicKey Hexdecode = KeyUtil.decodeECPoint(encodeHex, SM2Constant.SM2_CURVE_NAME); + final PublicKey B64decode = KeyUtil.decodeECPoint(encodeB64, SM2Constant.SM2_CURVE_NAME); Assertions.assertEquals(HexUtil.encodeStr(publicKey.getEncoded()), HexUtil.encodeStr(Hexdecode.getEncoded())); Assertions.assertEquals(HexUtil.encodeStr(publicKey.getEncoded()), HexUtil.encodeStr(B64decode.getEncoded())); } @@ -286,7 +288,8 @@ public class SM2Test { final String priKey = "MHcCAQEEIE29XqAFV/rkJbnJzCoQRJLTeAHG2TR0h9ZCWag0+ZMEoAoGCCqBHM9VAYItoUQDQgAESkOzNigIsH5ehFvr9y" + "QNQ66genyOrm+Q4umCA4aWXPeRzmcTAWSlTineiReTFN2lqor2xaulT8u3a4w3AM/F6A=="; - final PrivateKey privateKey = KeyUtil.generatePrivateKey("sm2", new OpenSSHPrivateKeySpec(SecureUtil.decode(priKey))); + final PrivateKey privateKey = KeyUtil.generatePrivateKey("sm2", new OpenSSHPrivateKeySpec( + Objects.requireNonNull(SecureUtil.decode(priKey)))); final ECPrivateKeyParameters privateKeyParameters = ECKeyUtil.toPrivateParams(privateKey); final SM2 sm2 = new SM2(privateKeyParameters, ECKeyUtil.getPublicParams(privateKeyParameters)); diff --git a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/ECKeyUtilTest.java b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/ECKeyUtilTest.java index 5829f9bc3..08d02f510 100644 --- a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/ECKeyUtilTest.java +++ b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/ECKeyUtilTest.java @@ -18,9 +18,14 @@ package org.dromara.hutool.crypto.bc; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.jce.interfaces.ECPrivateKey; +import org.dromara.hutool.crypto.KeyUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.security.KeyPair; +import java.security.PublicKey; + public class ECKeyUtilTest { /** @@ -42,4 +47,11 @@ public class ECKeyUtilTest { final ECPrivateKeyParameters keyParameters = ECKeyUtil.toSm2PrivateParams(privateKeyHex); Assertions.assertNotNull(keyParameters); } + + @Test + void getECPublicKeyTest() { + final KeyPair sm2 = KeyUtil.generateKeyPair("sm2"); + final PublicKey ecPublicKey = ECKeyUtil.getECPublicKey((ECPrivateKey) sm2.getPrivate(), SM2Constant.SM2_EC_SPEC); + Assertions.assertEquals(sm2.getPublic(), ecPublicKey); + } }