add ECKeySpecUtil

This commit is contained in:
Looly 2024-09-09 01:26:00 +08:00
parent 7468032a8d
commit 9281b0f8e6
11 changed files with 551 additions and 153 deletions

View File

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

View File

@ -89,7 +89,7 @@ public interface AsymmetricDecryptor {
*
* @param data 数据Hex16进制或Base64字符串
* @param keyType 密钥类型
* @return 解密后的密文
* @return 解密后的密文UTF-8编码
* @since 4.5.2
*/
default String decryptStr(final String data, final KeyType keyType) {

View File

@ -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<SM2> {
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<SM2> {
*/
public SM2(final byte[] privateKey, final byte[] publicKey) {
this(
ECKeyUtil.decodePrivateKeyParams(privateKey),
ECKeyUtil.decodePublicKeyParams(publicKey)
ECKeyUtil.generateSm2PrivateKey(privateKey),
ECKeyUtil.generateSm2PublicKey(publicKey)
);
}
/**
* 构造 <br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
*
* @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;
}
}
/**
* 构造 <br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
@ -147,7 +138,11 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
* @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<SM2> {
* @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));
}
/**
* 构造 <br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
*
* @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<SM2> {
this.publicKeyParams = publicKeyParams;
this.init();
}
// ------------------------------------------------------------------ Constructor end
// endregion
/**
* 初始化<br>
@ -191,6 +200,7 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
*/
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<SM2> {
return this;
}
// --------------------------------------------------------------------------------- Encrypt
// region ----- Encrypt
/**
* 使用公钥加密SM2非对称加密的结果由C1,C3,C2三部分组成其中
*
* <pre>
* C1 生成随机数的计算出的椭圆曲线点
* C3 SM3的摘要值
* C2 密文数据
* </pre>
*
* @param data 被加密的字符串UTF8编码
* @return 加密后的Base64
* @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常
*/
public String encryptBase64(final String data) {
return encryptBase64(data, KeyType.PublicKey);
}
/**
* 使用公钥加密SM2非对称加密的结果由C1,C3,C2三部分组成其中
*
* <pre>
* C1 生成随机数的计算出的椭圆曲线点
* C3 SM3的摘要值
* C2 密文数据
* </pre>
*
* @param in 被加密的数据流
* @return 加密后的Base64
* @throws IORuntimeException IO异常
*/
public String encryptBase64(final InputStream in) throws IORuntimeException {
return encryptBase64(in, KeyType.PublicKey);
}
/**
* 使用公钥加密SM2非对称加密的结果由C1,C3,C2三部分组成其中
*
* <pre>
* C1 生成随机数的计算出的椭圆曲线点
* C3 SM3的摘要值
* C2 密文数据
* </pre>
*
* @param data 被加密的bytes
* @return 加密后的Base64
*/
public String encryptBase64(final byte[] data) {
return encryptBase64(data, KeyType.PublicKey);
}
/**
* 使用公钥加密SM2非对称加密的结果由C1,C3,C2三部分组成其中
*
* <pre>
* C1 生成随机数的计算出的椭圆曲线点
* C3 SM3的摘要值
* C2 密文数据
* </pre>
*
* @param data 被加密的字符串UTF8编码
* @return 加密后的bytes
* @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常
*/
public byte[] encrypt(final String data) {
return encrypt(data, KeyType.PublicKey);
}
/**
* 使用公钥加密SM2非对称加密的结果由C1,C3,C2三部分组成其中
*
* <pre>
* C1 生成随机数的计算出的椭圆曲线点
* C3 SM3的摘要值
* C2 密文数据
* </pre>
*
* @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<SM2> {
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密文数据Hex16进制或Base64字符串
* @return 解密后的字符串UTF-8 编码
*/
public String decryptStr(final String data) {
return decryptStr(data, KeyType.PrivateKey);
}
/**
* 使用私钥解密
*
* @param data SM2密文数据Hex16进制或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<SM2> {
*/
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 ) + C332个字节+ C2
// C1( 02 + X ) + C332个字节+ 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<SM2> {
lock.unlock();
}
}
// --------------------------------------------------------------------------------- Sign and Verify
//endregion
// region ----- Sign and Verify
/**
* 用私钥对信息生成数字签名
@ -458,6 +589,7 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
lock.unlock();
}
}
// endregion
@Override
public SM2 setPrivateKey(final PrivateKey privateKey) {
@ -513,6 +645,18 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
return this;
}
/**
* 设置是否移除压缩标记默认为false<br>
* 移除后的密文兼容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<SM2> {
this.digest.reset();
return this.signer;
}
/**
* 去除04压缩标识<br>
* 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;
}
/**
* 追加压缩标识<br>
* 检查数据gmssl等库生成的密文不包含04前缀非压缩数据标识此处检查并补充
* 参考https://blog.csdn.net/softt/article/details/139978608
* 根据公钥压缩形态不同密文分为两种压缩形式
* C1( 03 + X ) + C332个字节+ C2
* C1( 02 + X ) + C332个字节+ 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
}

View File

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

View File

@ -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
/**
* 只获取私钥里的d32位字节
*
* @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值<br>
* 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私钥支持包括
*
* <ul>
* <li>D值</li>
* <li>PKCS#8</li>
* <li>PKCS#1</li>
* <li>OpenSSH格式</li>
* </ul>
*
* @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公钥支持包括
*
* <ul>
* <li>Q值</li>
@ -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
}

View File

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

View File

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

View File

@ -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算法对象<br>
@ -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);
}

View File

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

View File

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

View File

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