This commit is contained in:
Looly 2020-03-02 23:34:45 +08:00
parent 1ae9d35d83
commit 82f982f809
31 changed files with 327 additions and 610 deletions

View File

@ -3,7 +3,7 @@
-------------------------------------------------------------------------------------------------------------
## 5.1.6
## 5.2.0
### 新特性
* 【core 】 NumberUtil.decimalFormat增加Object对象参数支持
@ -18,6 +18,8 @@
* 【all 】 log、template、tokenizer使用SPI机制代替硬编码
* 【poi 】 Word07Writer增加addPicture
* 【crypto】 RSA算法中BlockSize长度策略调整issue#721@Github
* 【crypto】 删除SM2Engine使用BC库中的对象替代
* 【crypto】 增加PemUtil工具类
### Bug修复

View File

@ -40,7 +40,7 @@
-- 主页:<a href="https://hutool.cn">https://hutool.cn/</a> | <a href="https://www.hutool.club/">https://www.hutool.club/</a> --
</p>
<p align="center">
-- QQ群③<a href="https://shang.qq.com/wpa/qunwpa?idkey=35764b2247c46ffebe28e45.1.6b2af8f5dee5efcf47ceec69d21e4521aa8c75">555368316</a> --
-- QQ群③<a href="https://shang.qq.com/wpa/qunwpa?idkey=35764b2247c46ffebe28e45.2.0b2af8f5dee5efcf47ceec69d21e4521aa8c75">555368316</a> --
-- QQ群④<a href="https://shang.qq.com/wpa/qunwpa?idkey=309056e409a304a454c7ba250a10d38dd82b9b49cd0e1f180fedbde78b02ae0d">718802356</a> --
</p>
@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本避免网络上参差不
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.6</version>
<version>5.2.0</version>
</dependency>
```
### Gradle
```
compile 'cn.hutool:hutool-all:5.1.6'
compile 'cn.hutool:hutool-all:5.2.0'
```
### 非Maven项目
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.1.6/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.1.6/)
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.2.0/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.2.0/)
> 注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类获工具方法可用。

View File

@ -1 +1 @@
5.1.6
5.2.0

View File

@ -1 +1 @@
var version = '5.1.6'
var version = '5.2.0'

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-all</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-aop</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-bloomFilter</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-bom</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-cache</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-captcha</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-core</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-cron</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-crypto</artifactId>

View File

@ -1,30 +1,25 @@
package cn.hutool.crypto;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
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.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemObjectGenerator;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
@ -81,7 +76,7 @@ public class BCUtil {
final ECPoint point = ECPointUtil.decodePoint(ecCurve, encodeByte);
// 根据曲线恢复公钥格式
ECParameterSpec ecSpec = new ECNamedCurveSpec(curveName, curve, namedSpec.getG(), namedSpec.getN());
ECNamedCurveSpec ecSpec = new ECNamedCurveSpec(curveName, curve, namedSpec.getG(), namedSpec.getN());
final KeyFactory PubKeyGen = KeyUtil.getKeyFactory("EC");
try {
@ -92,152 +87,37 @@ public class BCUtil {
}
/**
* 读取PEM格式的私钥
* ECKey转换为ECKeyParameters
*
* @param pemStream pem流
* @return {@link PrivateKey}
* @since 4.5.2
*/
public static PrivateKey readPrivateKey(InputStream pemStream) {
return (PrivateKey) readPemKey(pemStream);
}
/**
* 读取PEM格式的公钥
*
* @param pemStream pem流
* @return {@link PublicKey}
* @since 4.5.2
*/
public static PublicKey readPublicKey(InputStream pemStream) {
return (PublicKey) readPemKey(pemStream);
}
/**
* 从pem文件中读取公钥或私钥<br>
* 根据类型返回{@link PublicKey} 或者 {@link PrivateKey}
*
* @param pemKeyStream pem流
* @return {@link Key}
* @since 4.5.2
* @deprecated 请使用{@link #readPemKey(InputStream)}
*/
@Deprecated
public static Key readKey(InputStream pemKeyStream) {
return readPemKey(pemKeyStream);
}
/**
* 从pem文件中读取公钥或私钥<br>
* 根据类型返回{@link PublicKey} 或者 {@link PrivateKey}
*
* @param keyStream pem流
* @return {@link Key}null表示无法识别的密钥类型
* @param ecKey BCECPrivateKey或者BCECPublicKey
* @return ECPrivateKeyParameters或者ECPublicKeyParameters
* @since 5.1.6
*/
public static Key readPemKey(InputStream keyStream) {
final PemObject object = readPemObject(keyStream);
final String type = object.getType();
if (StrUtil.isNotBlank(type)) {
if (type.endsWith("PRIVATE KEY")) {
return KeyUtil.generateRSAPrivateKey(object.getContent());
} else if (type.endsWith("PUBLIC KEY")) {
return KeyUtil.generateRSAPublicKey(object.getContent());
} else if (type.endsWith("CERTIFICATE")) {
return KeyUtil.readPublicKeyFromCert(IoUtil.toStream(object.getContent()));
}
public static ECKeyParameters toParams(ECKey ecKey) {
final ECParameterSpec parameterSpec = ecKey.getParameters();
final ECDomainParameters ecDomainParameters = buildECDomainParameters(parameterSpec);
if (ecKey instanceof BCECPrivateKey) {
return new ECPrivateKeyParameters(((BCECPrivateKey) ecKey).getD(), ecDomainParameters);
} else if (ecKey instanceof BCECPublicKey) {
return new ECPublicKeyParameters(((BCECPublicKey) ecKey).getQ(), ecDomainParameters);
}
//表示无法识别的密钥类型
return null;
}
/**
* 从pem文件中读取公钥或私钥
* 构建ECDomainParameters对象
*
* @param keyStream pem流
* @return 密钥bytes
* @since 4.5.2
* @deprecated 使用{@link #readPem(InputStream)}
*/
@Deprecated
public static byte[] readKeyBytes(InputStream keyStream) {
return readPem(keyStream);
}
/**
* 从pem流中读取公钥或私钥
*
* @param keyStream pem流
* @return 密钥bytes
* @param parameterSpec ECParameterSpec
* @return ECDomainParameters
* @since 5.1.6
*/
public static byte[] readPem(InputStream keyStream) {
PemObject pemObject = readPemObject(keyStream);
if (null != pemObject) {
return pemObject.getContent();
}
return null;
}
/**
* 读取pem文件中的信息包括类型头信息和密钥内容
*
* @param keyStream pem流
* @return {@link PemObject}
* @since 4.5.2
*/
public static PemObject readPemObject(InputStream keyStream) {
return readPemObject(IoUtil.getUtf8Reader(keyStream));
}
/**
* 读取pem文件中的信息包括类型头信息和密钥内容
*
* @param reader pem Reader
* @return {@link PemObject}
* @since 5.1.6
*/
public static PemObject readPemObject(Reader reader) {
PemReader pemReader = null;
try {
pemReader = new PemReader(reader);
return pemReader.readPemObject();
} catch (IOException e) {
throw new IORuntimeException(e);
} finally {
IoUtil.close(pemReader);
}
}
/**
* 写出pem密钥私钥公钥证书
*
* @param type 密钥类型私钥公钥证书
* @param content 密钥内容
* @param keyStream pem流
* @since 5.1.6
*/
public static void writePemObject(String type, byte[] content, OutputStream keyStream) {
writePemObject(new PemObject(type, content), keyStream);
}
/**
* 写出pem密钥私钥公钥证书
*
* @param pemObject pem对象包括密钥和密钥类型等信息
* @param keyStream pem流
* @since 5.1.6
*/
public static void writePemObject(PemObjectGenerator pemObject, OutputStream keyStream) {
PemWriter writer = null;
try {
writer = new PemWriter(IoUtil.getUtf8Writer(keyStream));
writer.writeObject(pemObject);
} catch (IOException e) {
throw new IORuntimeException(e);
} finally {
IoUtil.close(writer);
}
public static ECDomainParameters buildECDomainParameters(ECParameterSpec parameterSpec) {
return new ECDomainParameters(
parameterSpec.getCurve(),
parameterSpec.getG(),
parameterSpec.getN(),
parameterSpec.getH());
}
}

View File

@ -0,0 +1,154 @@
package cn.hutool.crypto;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemObjectGenerator;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* PEM(Privacy Enhanced Mail)格式相关工具类
*
* <p>
* PEM一般为文本格式 -----BEGIN... 开头 -----END... 结尾中间的内容是 BASE64 编码
* <p>
* 这种格式可以保存证书和私钥有时我们也把PEM格式的私钥的后缀改为 .key 以区别证书与私钥
*
* @author looly
* @since 5.1.6
*/
public class PemUtil {
/**
* 读取PEM格式的私钥
*
* @param pemStream pem流
* @return {@link PrivateKey}
* @since 4.5.2
*/
public static PrivateKey readPemPrivateKey(InputStream pemStream) {
return (PrivateKey) PemUtil.readPemKey(pemStream);
}
/**
* 读取PEM格式的公钥
*
* @param pemStream pem流
* @return {@link PublicKey}
* @since 4.5.2
*/
public static PublicKey readPemPublicKey(InputStream pemStream) {
return (PublicKey) PemUtil.readPemKey(pemStream);
}
/**
* 从pem文件中读取公钥或私钥<br>
* 根据类型返回 {@link PublicKey} 或者 {@link PrivateKey}
*
* @param keyStream pem流
* @return {@link Key}null表示无法识别的密钥类型
* @since 5.1.6
*/
public static Key readPemKey(InputStream keyStream) {
final PemObject object = readPemObject(keyStream);
final String type = object.getType();
if (StrUtil.isNotBlank(type)) {
if (type.endsWith("PRIVATE KEY")) {
return KeyUtil.generateRSAPrivateKey(object.getContent());
} else if (type.endsWith("PUBLIC KEY")) {
return KeyUtil.generateRSAPublicKey(object.getContent());
} else if (type.endsWith("CERTIFICATE")) {
return KeyUtil.readPublicKeyFromCert(IoUtil.toStream(object.getContent()));
}
}
//表示无法识别的密钥类型
return null;
}
/**
* 从pem流中读取公钥或私钥
*
* @param keyStream pem流
* @return 密钥bytes
* @since 5.1.6
*/
public static byte[] readPem(InputStream keyStream) {
PemObject pemObject = readPemObject(keyStream);
if (null != pemObject) {
return pemObject.getContent();
}
return null;
}
/**
* 读取pem文件中的信息包括类型头信息和密钥内容
*
* @param keyStream pem流
* @return {@link PemObject}
* @since 4.5.2
*/
public static PemObject readPemObject(InputStream keyStream) {
return readPemObject(IoUtil.getUtf8Reader(keyStream));
}
/**
* 读取pem文件中的信息包括类型头信息和密钥内容
*
* @param reader pem Reader
* @return {@link PemObject}
* @since 5.1.6
*/
public static PemObject readPemObject(Reader reader) {
PemReader pemReader = null;
try {
pemReader = new PemReader(reader);
return pemReader.readPemObject();
} catch (IOException e) {
throw new IORuntimeException(e);
} finally {
IoUtil.close(pemReader);
}
}
/**
* 写出pem密钥私钥公钥证书
*
* @param type 密钥类型私钥公钥证书
* @param content 密钥内容
* @param keyStream pem流
* @since 5.1.6
*/
public static void writePemObject(String type, byte[] content, OutputStream keyStream) {
writePemObject(new PemObject(type, content), keyStream);
}
/**
* 写出pem密钥私钥公钥证书
*
* @param pemObject pem对象包括密钥和密钥类型等信息
* @param keyStream pem流
* @since 5.1.6
*/
public static void writePemObject(PemObjectGenerator pemObject, OutputStream keyStream) {
PemWriter writer = null;
try {
writer = new PemWriter(IoUtil.getUtf8Writer(keyStream));
writer.writeObject(pemObject);
} catch (IOException e) {
throw new IORuntimeException(e);
} finally {
IoUtil.close(writer);
}
}
}

View File

@ -2,8 +2,9 @@ package cn.hutool.crypto.asymmetric;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.SM2Engine.SM2Mode;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
@ -19,23 +20,26 @@ import java.security.PublicKey;
* 国密SM2算法实现基于BC库<br>
* SM2算法只支持公钥加密私钥解密<br>
* 参考https://blog.csdn.net/pridas/article/details/86118774
*
*
* @author looly
* @since 4.3.2
*/
public class SM2 extends AbstractAsymmetricCrypto<SM2> {
/** 算法EC */
/**
* 算法EC
*/
private static final String ALGORITHM_SM2 = "SM2";
protected SM2Engine engine;
protected SM2Signer signer;
private SM2Mode mode;
private SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
private ECPublicKeyParameters publicKeyParams;
private ECPrivateKeyParameters privateKeyParams;
// ------------------------------------------------------------------ Constructor start
/**
* 构造生成新的私钥公钥对
*/
@ -47,9 +51,9 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
* 构造<br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
*
*
* @param privateKeyStr 私钥Hex或Base64表示
* @param publicKeyStr 公钥Hex或Base64表示
* @param publicKeyStr 公钥Hex或Base64表示
*/
public SM2(String privateKeyStr, String publicKeyStr) {
this(SecureUtil.decode(privateKeyStr), SecureUtil.decode(publicKeyStr));
@ -59,9 +63,9 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
* 构造 <br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
*
*
* @param privateKey 私钥
* @param publicKey 公钥
* @param publicKey 公钥
*/
public SM2(byte[] privateKey, byte[] publicKey) {
this(//
@ -74,9 +78,9 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
* 构造 <br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
*
*
* @param privateKey 私钥
* @param publicKey 公钥
* @param publicKey 公钥
*/
public SM2(PrivateKey privateKey, PublicKey publicKey) {
super(ALGORITHM_SM2, privateKey, publicKey);
@ -87,9 +91,9 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
* 初始化<br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密签名或者解密校验
*
*
* @param privateKey 私钥
* @param publicKey 公钥
* @param publicKey 公钥
* @return this
*/
public SM2 init(PrivateKey privateKey, PublicKey publicKey) {
@ -103,16 +107,17 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
}
// --------------------------------------------------------------------------------- Encrypt
/**
* 加密SM2非对称加密的结果由C1,C2,C3三部分组成其中
*
*
* <pre>
* C1 生成随机数的计算出的椭圆曲线点
* C2 密文数据
* C3 SM3的摘要值
* </pre>
*
* @param data 被加密的bytes
*
* @param data 被加密的bytes
* @param keyType 私钥或公钥 {@link KeyType}
* @return 加密后的bytes
* @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常
@ -123,22 +128,43 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
throw new IllegalArgumentException("Encrypt is only support by public key");
}
checkKey(keyType);
return encrypt(data, new ParametersWithRandom(getCipherParameters(keyType)));
}
/**
* 加密SM2非对称加密的结果由C1,C2,C3三部分组成其中
*
* <pre>
* C1 生成随机数的计算出的椭圆曲线点
* C2 密文数据
* C3 SM3的摘要值
* </pre>
*
* @param data 被加密的bytes
* @param pubKeyParameters 公钥参数
* @return 加密后的bytes
* @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常
* @since 5.1.6
*/
public byte[] encrypt(byte[] data, CipherParameters pubKeyParameters) throws CryptoException {
lock.lock();
final SM2Engine engine = getEngine();
try {
engine.init(true, new ParametersWithRandom(getCipherParameters(keyType)));
engine.init(true, pubKeyParameters);
return engine.processBlock(data, 0, data.length);
} catch (InvalidCipherTextException e) {
throw new CryptoException(e);
} finally {
lock.unlock();
}
}
// --------------------------------------------------------------------------------- Decrypt
/**
* 解密
*
* @param data SM2密文实际包含三部分ECC公钥真正的密文公钥和原文的SM3-HASH值
*
* @param data SM2密文实际包含三部分ECC公钥真正的密文公钥和原文的SM3-HASH值
* @param keyType 私钥或公钥 {@link KeyType}
* @return 加密后的bytes
* @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常
@ -149,21 +175,35 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
throw new IllegalArgumentException("Decrypt is only support by private key");
}
checkKey(keyType);
return decrypt(data, getCipherParameters(keyType));
}
/**
* 解密
*
* @param data SM2密文实际包含三部分ECC公钥真正的密文公钥和原文的SM3-HASH值
* @param privateKeyParameters 私钥参数
* @return 加密后的bytes
* @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常
* @since 5.1.6
*/
public byte[] decrypt(byte[] data, CipherParameters privateKeyParameters) throws CryptoException {
lock.lock();
final SM2Engine engine = getEngine();
try {
engine.init(false, getCipherParameters(keyType));
engine.init(false, privateKeyParameters);
return engine.processBlock(data, 0, data.length);
} catch (InvalidCipherTextException e) {
throw new CryptoException(e);
} finally {
lock.unlock();
}
}
// --------------------------------------------------------------------------------- Sign and Verify
/**
* 用私钥对信息生成数字签名
*
*
* @param data 加密数据
* @return 签名
*/
@ -173,9 +213,9 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
/**
* 用私钥对信息生成数字签名
*
*
* @param data 加密数据
* @param id 可以为null若为null则默认withId为字节数组:"1234567812345678".getBytes()
* @param id 可以为null若为null则默认withId为字节数组:"1234567812345678".getBytes()
* @return 签名
*/
public byte[] sign(byte[] data, byte[] id) {
@ -198,7 +238,7 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
/**
* 用公钥检验数字签名的合法性
*
*
* @param data 数据
* @param sign 签名
* @return 是否验证通过
@ -209,10 +249,10 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
/**
* 用公钥检验数字签名的合法性
*
*
* @param data 数据
* @param sign 签名
* @param id 可以为null若为null则默认withId为字节数组:"1234567812345678".getBytes()
* @param id 可以为null若为null则默认withId为字节数组:"1234567812345678".getBytes()
* @return 是否验证通过
*/
public boolean verify(byte[] data, byte[] sign, byte[] id) {
@ -257,22 +297,23 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
/**
* 设置加密类型
*
* @param mode {@link SM2Mode}
*
* @param mode {@link SM2Engine.Mode}
* @return this
*/
public SM2 setMode(SM2Mode mode) {
public SM2 setMode(SM2Engine.Mode mode) {
this.mode = mode;
if (null != this.engine) {
this.engine.setMode(mode);
this.engine = null;
}
return this;
}
// ------------------------------------------------------------------------------------------------------------------------- Private method start
/**
* 初始化加密解密参数包括私钥和公钥参数
*
*
* @return this
*/
private SM2 initCipherParams() {
@ -292,16 +333,16 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
/**
* 获取密钥类型对应的加密参数对象{@link CipherParameters}
*
*
* @param keyType Key类型枚举包括私钥或公钥
* @return {@link CipherParameters}
*/
private CipherParameters getCipherParameters(KeyType keyType) {
switch (keyType) {
case PublicKey:
return this.publicKeyParams;
case PrivateKey:
return this.privateKeyParams;
case PublicKey:
return this.publicKeyParams;
case PrivateKey:
return this.privateKeyParams;
}
return null;
@ -309,27 +350,27 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
/**
* 检查对应类型的Key是否存在
*
*
* @param keyType key类型
*/
private void checkKey(KeyType keyType) {
switch (keyType) {
case PublicKey:
if (null == this.publicKey) {
throw new NullPointerException("No public key provided");
}
break;
case PrivateKey:
if (null == this.privateKey) {
throw new NullPointerException("No private key provided");
}
break;
case PublicKey:
if (null == this.publicKey) {
throw new NullPointerException("No public key provided");
}
break;
case PrivateKey:
if (null == this.privateKey) {
throw new NullPointerException("No private key provided");
}
break;
}
}
/**
* 获取{@link SM2Engine}
*
* 获取{@link SM2Engine}此对象为懒加载模式
*
* @return {@link SM2Engine}
*/
private SM2Engine getEngine() {
@ -338,10 +379,10 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
}
return this.engine;
}
/**
* 获取{@link SM2Signer}
*
* 获取{@link SM2Signer}此对象为懒加载模式
*
* @return {@link SM2Signer}
*/
private SM2Signer getSigner() {

View File

@ -1,356 +0,0 @@
package cn.hutool.crypto.asymmetric;
import java.math.BigInteger;
import java.util.Random;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SM3Digest;
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.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECConstants;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECMultiplier;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.CryptoException;
/**
* SM2加密解密引擎来自Bouncy Castle库的SM2Engine类改造<br>
* SM2加密后的数据格式为两种模式
*
* <pre>
* curve(C1) | data(C2) | digest(C3)
* curve(C1) | digest(C3) | data(C2)
* </pre>
*
* @author looly, bouncycastle
* @since 4.5.0
*/
public class SM2Engine {
private final Digest digest;
private boolean forEncryption;
private ECKeyParameters ecKey;
private ECDomainParameters ecParams;
private int curveLength;
private Random random;
/** 加密解密模式 */
private SM2Mode mode;
/**
* 构造
*/
public SM2Engine() {
this(new SM3Digest());
}
/**
* 构造
*
* @param mode SM2密钥生成模式可选C1C2C3和C1C3C2
*/
public SM2Engine(SM2Mode mode) {
this(new SM3Digest(), mode);
}
/**
* 构造
*
* @param digest 摘要算法啊
*/
public SM2Engine(Digest digest) {
this(digest, null);
}
/**
* 构造
*
* @param digest 摘要算法啊
* @param mode SM2密钥生成模式可选C1C2C3和C1C3C2
*/
public SM2Engine(Digest digest, SM2Mode mode) {
this.digest = digest;
this.mode = ObjectUtil.defaultIfNull(mode, SM2Mode.C1C3C2);
}
/**
* 初始化引擎
*
* @param forEncryption 是否为加密模式
* @param param {@link CipherParameters}此处应为{@link ParametersWithRandom}加密时{@link ECKeyParameters}解密时
*/
public void init(boolean forEncryption, CipherParameters param) {
this.forEncryption = forEncryption;
if (param instanceof ParametersWithRandom) {
final ParametersWithRandom rParam = (ParametersWithRandom) param;
this.ecKey = (ECKeyParameters) rParam.getParameters();
this.random = rParam.getRandom();
} else {
this.ecKey = (ECKeyParameters) param;
}
this.ecParams = this.ecKey.getParameters();
if (forEncryption) {
// 检查曲线点
final ECPoint ecPoint = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
if (ecPoint.isInfinity()) {
throw new IllegalArgumentException("invalid key: [h]Q at infinity");
}
// 检查随机参数
if (null == this.random) {
this.random = CryptoServicesRegistrar.getSecureRandom();
}
}
// 曲线位长度
this.curveLength = (this.ecParams.getCurve().getFieldSize() + 7) / 8;
}
/**
* 处理块包括加密和解密
*
* @param in 数据
* @param inOff 数据开始位置
* @param inLen 数据长度
* @return 结果
*/
public byte[] processBlock(byte[] in, int inOff, int inLen) {
if (forEncryption) {
return encrypt(in, inOff, inLen);
} else {
return decrypt(in, inOff, inLen);
}
}
/**
* 设置加密类型
*
* @param mode {@link SM2Mode}
* @return this
*/
public SM2Engine setMode(SM2Mode mode) {
this.mode = mode;
return this;
}
/**
* SM2算法模式<br>
* 在SM2算法中C1C2C3为旧标准模式C1C3C2为新标准模式
*
* @author looly
*
*/
public enum SM2Mode {
C1C2C3, C1C3C2
}
protected ECMultiplier createBasePointMultiplier() {
return new FixedPointCombMultiplier();
}
// --------------------------------------------------------------------------------------------------- Private method start
/**
* 加密
*
* @param in 数据
* @param inOff 位置
* @param inLen 长度
* @return 密文
*/
private byte[] encrypt(byte[] in, int inOff, int inLen) {
// 加密数据
byte[] c2 = new byte[inLen];
System.arraycopy(in, inOff, c2, 0, c2.length);
final ECMultiplier multiplier = createBasePointMultiplier();
byte[] c1;
ECPoint kPB;
BigInteger k;
do {
k = nextK();
// 产生随机数计算出曲线点C1
c1 = multiplier.multiply(ecParams.getG(), k).normalize().getEncoded(false);
kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();
kdf(kPB, c2);
} while (notEncrypted(c2, in, inOff));
// 杂凑值效验数据
byte[] c3 = new byte[digest.getDigestSize()];
addFieldElement(kPB.getAffineXCoord());
this.digest.update(in, inOff, inLen);
addFieldElement(kPB.getAffineYCoord());
this.digest.doFinal(c3, 0);
// 按照对应模式输出结果
if (mode == SM2Mode.C1C3C2) {
return Arrays.concatenate(c1, c3, c2);
}
return Arrays.concatenate(c1, c2, c3);
}
/**
* 解密只支持私钥解密
*
* @param in 密文
* @param inOff 位置
* @param inLen 长度
* @return 解密后的内容
*/
private byte[] decrypt(byte[] in, int inOff, int inLen) {
// 获取曲线点
final byte[] c1 = new byte[this.curveLength * 2 + 1];
System.arraycopy(in, inOff, c1, 0, c1.length);
ECPoint c1P = this.ecParams.getCurve().decodePoint(c1);
if (c1P.multiply(this.ecParams.getH()).isInfinity()) {
throw new CryptoException("[h]C1 at infinity");
}
c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();
final int digestSize = this.digest.getDigestSize();
// 解密C2数据
final byte[] c2 = new byte[inLen - c1.length - digestSize];
if (SM2Mode.C1C3C2 == this.mode) {
// C2位于第三部分
System.arraycopy(in, inOff + c1.length + digestSize, c2, 0, c2.length);
} else {
// C2位于第二部分
System.arraycopy(in, inOff + c1.length, c2, 0, c2.length);
}
kdf(c1P, c2);
// 使用摘要验证C2数据
final byte[] c3 = new byte[digestSize];
addFieldElement(c1P.getAffineXCoord());
this.digest.update(c2, 0, c2.length);
addFieldElement(c1P.getAffineYCoord());
this.digest.doFinal(c3, 0);
int check = 0;
for (int i = 0; i != c3.length; i++) {
check |= c3[i] ^ in[inOff + c1.length + ((SM2Mode.C1C3C2 == this.mode) ? 0 : c2.length) + i];
}
Arrays.fill(c1, (byte) 0);
Arrays.fill(c3, (byte) 0);
if (check != 0) {
Arrays.fill(c2, (byte) 0);
throw new CryptoException("invalid cipher text");
}
return c2;
}
private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
for (int i = 0; i != encData.length; i++) {
if (encData[i] != in[inOff + i]) {
return false;
}
}
return true;
}
/**
* 解密数据
*
* @param c1 c1点
* @param encData 密文
*/
private void kdf(ECPoint c1, byte[] encData) {
final Digest digest = this.digest;
int digestSize = digest.getDigestSize();
byte[] buf = new byte[Math.max(4, digestSize)];
int off = 0;
Memoable memo = null;
Memoable copy = null;
if (digest instanceof Memoable) {
addFieldElement(c1.getAffineXCoord());
addFieldElement(c1.getAffineYCoord());
memo = (Memoable) digest;
copy = memo.copy();
}
int ct = 0;
while (off < encData.length) {
if (memo != null) {
memo.reset(copy);
} else {
addFieldElement(c1.getAffineXCoord());
addFieldElement(c1.getAffineYCoord());
}
Pack.intToBigEndian(++ct, buf, 0);
digest.update(buf, 0, 4);
digest.doFinal(buf, 0);
int xorLen = Math.min(digestSize, encData.length - off);
xor(encData, buf, off, xorLen);
off += xorLen;
}
}
/**
* 异或
*
* @param data 数据
* @param kdfOut kdf输出值
* @param dOff d偏移
* @param dRemaining d剩余
*/
private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
for (int i = 0; i != dRemaining; i++) {
data[dOff + i] ^= kdfOut[i];
}
}
/**
* 下一个K值
*
* @return K值
*/
private BigInteger nextK() {
final int qBitLength = this.ecParams.getN().bitLength();
BigInteger k;
do {
k = new BigInteger(qBitLength, this.random);
} while (k.equals(ECConstants.ZERO) || k.compareTo(this.ecParams.getN()) >= 0);
return k;
}
/**
* 增加字段节点
*
* @param v 节点
*/
private void addFieldElement(ECFieldElement v) {
final byte[] p = BigIntegers.asUnsignedByteArray(this.curveLength, v.toBigInteger());
this.digest.update(p, 0, p.length);
}
// --------------------------------------------------------------------------------------------------- Private method start
}

View File

@ -1,40 +1,39 @@
package cn.hutool.crypto.test;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.crypto.PemUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import org.junit.Assert;
import org.junit.Test;
import java.security.PrivateKey;
import java.security.PublicKey;
import org.junit.Assert;
import org.junit.Test;
public class PemUtilTest {
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
public class BCUtilTest {
@Test
public void readPrivateKeyTest() {
PrivateKey privateKey = BCUtil.readPrivateKey(ResourceUtil.getStream("test_private_key.pem"));
PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_private_key.pem"));
Assert.assertNotNull(privateKey);
}
@Test
public void readPublicKeyTest() {
PublicKey publicKey = BCUtil.readPublicKey(ResourceUtil.getStream("test_public_key.csr"));
PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_public_key.csr"));
Assert.assertNotNull(publicKey);
}
@Test
public void readPemKeyTest() {
PublicKey publicKey = (PublicKey) BCUtil.readPemKey(ResourceUtil.getStream("test_public_key.csr"));
PublicKey publicKey = (PublicKey) PemUtil.readPemKey(ResourceUtil.getStream("test_public_key.csr"));
Assert.assertNotNull(publicKey);
}
@Test
public void validateKey() {
PrivateKey privateKey = BCUtil.readPrivateKey(ResourceUtil.getStream("test_private_key.pem"));
PublicKey publicKey = BCUtil.readPublicKey(ResourceUtil.getStream("test_public_key.csr"));
PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_private_key.pem"));
PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_public_key.csr"));
RSA rsa = new RSA(privateKey, publicKey);
String str = "你好Hutool";//测试字符串

View File

@ -1,13 +1,5 @@
package cn.hutool.crypto.test;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Test;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
@ -17,7 +9,13 @@ import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.asymmetric.SM2Engine.SM2Mode;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.junit.Assert;
import org.junit.Test;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* SM2算法单元测试
@ -48,10 +46,9 @@ public class SM2Test {
KeyPair pair = SecureUtil.generateKeyPair("SM2");
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
Console.log(HexUtil.encodeHexStr(publicKey));
SM2 sm2 = SmUtil.sm2(privateKey, publicKey);
sm2.setMode(SM2Mode.C1C3C2);
sm2.setMode(SM2Engine.Mode.C1C2C3);
// 公钥加密私钥解密
byte[] encrypt = sm2.encrypt(StrUtil.bytes("我是一段测试aaaa", CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey);

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-db</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-dfa</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-extra</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-http</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-json</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-log</artifactId>

View File

@ -8,7 +8,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-poi</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-script</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-setting</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-socket</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-system</artifactId>

View File

@ -8,7 +8,7 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.1.6-SNAPSHOT</version>
<version>5.2.0-SNAPSHOT</version>
<name>hutool</name>
<description>提供丰富的Java工具方法</description>
<url>https://github.com/looly/hutool</url>