add method

This commit is contained in:
Looly 2021-02-26 22:50:35 +08:00
parent ba8b4ad760
commit 32c4952d31
10 changed files with 243 additions and 34 deletions

View File

@ -3,7 +3,7 @@
-------------------------------------------------------------------------------------------------------------
# 5.5.9 (2021-02-25)
# 5.5.9 (2021-02-26)
### 新特性
* 【crypto 】 PemUtil.readPemKey支持ECpr#1366@Github
@ -13,6 +13,7 @@
* 【cache 】 AbstractCache增加keySet方法issue#I37Z4C@Gitee
* 【core 】 NumberWordFormatter增加formatSimple方法pr#1436@Github
* 【crypto 】 增加读取openSSL生成的sm2私钥
* 【crypto 】 增加众多方法SM2兼容各类密钥格式issue#I37Z75@Gitee
### Bug修复
* 【json 】 JSONUtil.isJson方法改变trim策略解决特殊空白符导致判断失败问题

View File

@ -11,6 +11,7 @@ import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@ -694,4 +695,14 @@ public class CollUtilTest {
Assert.assertEquals(0, CollUtil.page(3, 5, objects).size());
}
@Test
public void subtractToListTest(){
List<Long> list1 = Arrays.asList(1L, 2L, 3L);
List<Long> list2 = Arrays.asList(2L, 3L);
List<Long> result = CollUtil.subtractToList(list1, list2);
Assert.assertEquals(1, result.size());
Assert.assertEquals(1L, result.get(0), 1);
}
}

View File

@ -11,7 +11,11 @@ import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec;
import org.bouncycastle.jcajce.spec.OpenSSHPublicKeySpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.util.BigIntegers;
import java.io.IOException;
@ -20,6 +24,7 @@ import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.KeySpec;
/**
* EC密钥参数相关工具类封装
@ -45,7 +50,21 @@ public class ECKeyUtil {
return null;
}
/**
* 根据私钥参数获取公钥参数
*
* @param privateKeyParameters 私钥参数
* @return 公钥参数
* @since 5.5.9
*/
public static ECPublicKeyParameters getPublicParams(ECPrivateKeyParameters privateKeyParameters) {
final ECDomainParameters domainParameters = privateKeyParameters.getParameters();
final ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), privateKeyParameters.getD());
return new ECPublicKeyParameters(q, domainParameters);
}
//--------------------------------------------------------------------------- Public Key
/**
* 转换为 ECPublicKeyParameters
*
@ -91,8 +110,8 @@ public class ECKeyUtil {
/**
* 转换为ECPublicKeyParameters
*
* @param x 公钥X
* @param y 公钥Y
* @param x 公钥X
* @param y 公钥Y
* @param domainParameters ECDomainParameters
* @return ECPublicKeyParametersx或y为{@code null}则返回{@code null}
*/
@ -109,7 +128,7 @@ public class ECKeyUtil {
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toPublicParams(byte[] xBytes, byte[] yBytes, ECDomainParameters domainParameters) {
if(null == xBytes || null == yBytes){
if (null == xBytes || null == yBytes) {
return null;
}
return toPublicParams(BigIntegers.fromUnsignedByteArray(xBytes), BigIntegers.fromUnsignedByteArray(yBytes), domainParameters);
@ -187,6 +206,7 @@ public class ECKeyUtil {
}
//--------------------------------------------------------------------------- Private Key
/**
* 转换为 ECPrivateKeyParameters
*
@ -220,7 +240,7 @@ public class ECKeyUtil {
/**
* 转换为 ECPrivateKeyParameters
*
* @param d 私钥d值16进制字符串
* @param d 私钥d值16进制字符串
* @param domainParameters ECDomainParameters
* @return ECPrivateKeyParameters
*/
@ -272,10 +292,11 @@ public class ECKeyUtil {
/**
* 将SM2算法的{@link ECPrivateKey} 转换为 {@link PrivateKey}
*
* @param privateKey {@link ECPrivateKey}
* @return {@link PrivateKey}
*/
public static PrivateKey toSm2PrivateKey(ECPrivateKey privateKey){
public static PrivateKey toSm2PrivateKey(ECPrivateKey privateKey) {
try {
final PrivateKeyInfo info = new PrivateKeyInfo(
new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SmUtil.ID_SM2_PUBLIC_KEY_PARAM), privateKey);
@ -284,4 +305,92 @@ public class ECKeyUtil {
throw new IORuntimeException(e);
}
}
/**
* 创建{@link OpenSSHPrivateKeySpec}
*
* @param key 私钥需为PKCS#1格式
* @return {@link OpenSSHPrivateKeySpec}
* @since 5.5.9
*/
public static KeySpec createOpenSSHPrivateKeySpec(byte[] key) {
return new OpenSSHPrivateKeySpec(key);
}
/**
* 创建{@link OpenSSHPublicKeySpec}
*
* @param key 公钥需为PKCS#1格式
* @return {@link OpenSSHPublicKeySpec}
* @since 5.5.9
*/
public static KeySpec createOpenSSHPublicKeySpec(byte[] key) {
return new OpenSSHPublicKeySpec(key);
}
/**
* 尝试解析转换各种类型私钥为{@link ECPrivateKeyParameters}支持包括
*
* <ul>
* <li>D值</li>
* <li>PKCS#8</li>
* <li>PKCS#1</li>
* </ul>
*
* @param privateKeyBytes 私钥
* @return {@link ECPrivateKeyParameters}
* @since 5.5.9
*/
public static ECPrivateKeyParameters decodePrivateKeyParams(byte[] privateKeyBytes) {
try {
// 尝试D值
return toSm2PrivateParams(privateKeyBytes);
} catch (Exception ignore) {
// ignore
}
PrivateKey privateKey;
//尝试PKCS#8
try {
privateKey = KeyUtil.generatePrivateKey("sm2", privateKeyBytes);
} catch (Exception ignore) {
// 尝试PKCS#1
privateKey = KeyUtil.generatePrivateKey("sm2", createOpenSSHPrivateKeySpec(privateKeyBytes));
}
return toPrivateParams(privateKey);
}
/**
* 尝试解析转换各种类型公钥为{@link ECPublicKeyParameters}支持包括
*
* <ul>
* <li>Q值</li>
* <li>X.509</li>
* <li>PKCS#1</li>
* </ul>
*
* @param publicKeyBytes 公钥
* @return {@link ECPublicKeyParameters}
* @since 5.5.9
*/
public static ECPublicKeyParameters decodePublicKeyParams(byte[] publicKeyBytes) {
try {
// 尝试Q值
return toSm2PublicParams(publicKeyBytes);
} catch (Exception ignore) {
// ignore
}
PublicKey publicKey;
//尝试X.509
try {
publicKey = KeyUtil.generatePublicKey("sm2", publicKeyBytes);
} catch (Exception ignore) {
// 尝试PKCS#1
publicKey = KeyUtil.generatePublicKey("sm2", createOpenSSHPublicKeySpec(publicKeyBytes));
}
return toPublicParams(publicKey);
}
}

View File

@ -256,8 +256,8 @@ public class KeyUtil {
* 采用PKCS#8规范此规范定义了私钥信息语法和加密私钥语法<br>
* 算法见https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyFactory
*
* @param algorithm 算法
* @param key 密钥必须为DER编码存储
* @param algorithm 算法如RSAECSM2等
* @param key 密钥PKCS#8格式
* @return 私钥 {@link PrivateKey}
*/
public static PrivateKey generatePrivateKey(String algorithm, byte[] key) {
@ -271,7 +271,7 @@ public class KeyUtil {
* 生成私钥仅用于非对称加密<br>
* 算法见https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyFactory
*
* @param algorithm 算法
* @param algorithm 算法如RSAECSM2等
* @param keySpec {@link KeySpec}
* @return 私钥 {@link PrivateKey}
* @since 3.1.1

View File

@ -3,7 +3,6 @@ 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.asn1.sec.ECPrivateKey;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemObjectGenerator;
import org.bouncycastle.util.io.pem.PemReader;
@ -134,14 +133,17 @@ public class PemUtil {
}
/**
* 读取OpenSSL生成的ANS1格式的Pem私钥文件
* 读取OpenSSL生成的ANS1格式的Pem私钥文件必须为PKCS#1格式
*
* @param keyStream 私钥pem流
* @return {@link PrivateKey}
*/
public static PrivateKey readSm2PemPrivateKey(InputStream keyStream) {
final ECPrivateKey ecPrivateKey = ECPrivateKey.getInstance(readPem(keyStream));
return ECKeyUtil.toSm2PrivateKey(ecPrivateKey);
try{
return KeyUtil.generatePrivateKey("sm2", ECKeyUtil.createOpenSSHPrivateKeySpec(readPem(keyStream)));
} finally {
IoUtil.close(keyStream);
}
}
/**

View File

@ -14,6 +14,8 @@ 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;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.StandardDSAEncoding;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
@ -22,11 +24,20 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* SM国密算法工具类<br>
* 此工具类依赖org.bouncycastle:bcprov-jdk15to18
*
* <p>封装包括</p>
* <ul>
* <li>SM2 椭圆曲线非对称加密和签名</li>
* <li>SM3 杂凑算法</li>
* <li>SM4 对称加密</li>
* </ul>
*
* @author looly
* @since 4.3.2
*/
@ -74,14 +85,42 @@ public class SmUtil {
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
*
* @param privateKey 私钥
* @param publicKey 公钥
* @param privateKey 私钥必须使用PKCS#8规范
* @param publicKey 公钥必须使用X509规范
* @return {@link SM2}
*/
public static SM2 sm2(byte[] privateKey, byte[] publicKey) {
return new SM2(privateKey, publicKey);
}
/**
* 创建SM2算法对象<br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
*
* @param privateKey 私钥
* @param publicKey 公钥
* @return {@link SM2}
* @since 5.5.9
*/
public static SM2 sm2(PrivateKey privateKey, PublicKey publicKey) {
return new SM2(privateKey, publicKey);
}
/**
* 创建SM2算法对象<br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
*
* @param privateKeyParams 私钥参数
* @param publicKeyParams 公钥参数
* @return {@link SM2}
* @since 5.5.9
*/
public static SM2 sm2(ECPrivateKeyParameters privateKeyParams, ECPublicKeyParameters publicKeyParams) {
return new SM2(privateKeyParams, publicKeyParams);
}
/**
* SM3加密<br>
* <br>
@ -192,8 +231,7 @@ public class SmUtil {
}
/**
* BC的SM3withSM2签名得到的结果的rs是asn1格式的这个方法转化成直接拼接r||s<br>
* 来自https://blog.csdn.net/pridas/article/details/86118774
* BC的SM3withSM2签名得到的结果的rs是asn1格式的这个方法转化成直接拼接r||s
*
* @param rsDer rs in asn1 format
* @return sign result in plain byte array
@ -214,8 +252,7 @@ public class SmUtil {
}
/**
* BC的SM3withSM2验签需要的rs是asn1格式的这个方法将直接拼接r||s的字节数组转化成asn1格式<br>
* 来自https://blog.csdn.net/pridas/article/details/86118774
* BC的SM3withSM2验签需要的rs是asn1格式的这个方法将直接拼接r||s的字节数组转化成asn1格式
*
* @param sign in plain byte array
* @return rs result in asn1 format

View File

@ -4,7 +4,7 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.ECKeyUtil;
import cn.hutool.crypto.SecureUtil;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
@ -74,13 +74,13 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做加密或者解密
*
* @param privateKey 私钥必须使用PKCS#8规范
* @param publicKey 公钥必须使用X509规范
* @param privateKey 私钥可以使用PKCS#8D值或PKCS#1规范
* @param publicKey 公钥可以使用X509Q值或PKCS#1规范
*/
public SM2(byte[] privateKey, byte[] publicKey) {
this(//
KeyUtil.generatePrivateKey(ALGORITHM_SM2, privateKey), //
KeyUtil.generatePublicKey(ALGORITHM_SM2, publicKey)//
this(
ECKeyUtil.decodePrivateKeyParams(privateKey),
ECKeyUtil.decodePublicKeyParams(publicKey)
);
}

View File

@ -1,7 +1,7 @@
package cn.hutool.crypto.digest;
/**
* SM3算法
* SM3杂凑算法
*
* @author looly
* @since 4.6.8

View File

@ -1,11 +1,14 @@
package cn.hutool.crypto.test;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.PemUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.SM2;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
@ -58,4 +61,20 @@ public class PemUtilTest {
// 64位签名
Assert.assertEquals(64, sign.length);
}
@Test
@Ignore
public void readECPrivateKeyTest2() {
// https://gitee.com/loolly/hutool/issues/I37Z75
byte[] d = PemUtil.readPem(FileUtil.getInputStream("d:/test/keys/priv.key"));
byte[] publicKey = PemUtil.readPem(FileUtil.getInputStream("d:/test/keys/pub.key"));
SM2 sm2 = new SM2(d, publicKey);
sm2.usePlainEncoding();
String content = "我是Hanley.";
byte[] sign = sm2.sign(StrUtil.utf8Bytes(content));
boolean verify = sm2.verify(StrUtil.utf8Bytes(content), sign);
Assert.assertTrue(verify);
}
}

View File

@ -12,7 +12,7 @@ import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec;
import org.junit.Assert;
import org.junit.Test;
@ -136,7 +136,7 @@ public class SM2Test {
//签名值
String signHex = "2881346e038d2ed706ccdd025f2b1dafa7377d5cf090134b98756fafe084dddbcdba0ab00b5348ed48025195af3f1dda29e819bb66aa9d4d088050ff148482a1";
final SM2 sm2 = new SM2(null, ECKeyUtil.toSm2PublicParams(publicKeyHex));
final SM2 sm2 = new SM2(null, publicKeyHex);
sm2.usePlainEncoding();
boolean verify = sm2.verify(dataBytes, HexUtil.decodeHex(signHex));
@ -251,10 +251,7 @@ public class SM2Test {
String data = "123456";
final ECPublicKeyParameters ecPublicKeyParameters = ECKeyUtil.toSm2PublicParams(q);
final ECPrivateKeyParameters ecPrivateKeyParameters = ECKeyUtil.toSm2PrivateParams(d);
final SM2 sm2 = new SM2(ecPrivateKeyParameters, ecPublicKeyParameters);
final SM2 sm2 = new SM2(d, q);
sm2.setMode(SM2Engine.Mode.C1C2C3);
final String encryptHex = sm2.encryptHex(data, KeyType.PublicKey);
final String decryptStr = sm2.decryptStr(encryptHex, KeyType.PrivateKey);
@ -277,8 +274,41 @@ public class SM2Test {
}
@Test
public void test(){
String priKey = "MHcCAQEEIE29XqAFV/rkJbnJzCoQRJLTeAHG2TR0h9ZCWag0+ZMEoAoGCCqBHM9VAYItoUQDQgAESkOzNigIsH5ehFvr9yQNQ66genyOrm+Q4umCA4aWXPeRzmcTAWSlTineiReTFN2lqor2xaulT8u3a4w3AM/F6A==";
public void getPublicKeyByPrivateKeyTest(){
// issue#I38SDPopenSSL生成的PKCS#1格式私钥
String priKey = "MHcCAQEEIE29XqAFV/rkJbnJzCoQRJLTeAHG2TR0h9ZCWag0+ZMEoAoGCCqBHM9VAYItoUQDQgAESkOzNigIsH5ehFvr9y" +
"QNQ66genyOrm+Q4umCA4aWXPeRzmcTAWSlTineiReTFN2lqor2xaulT8u3a4w3AM/F6A==";
PrivateKey privateKey = KeyUtil.generatePrivateKey("sm2", new OpenSSHPrivateKeySpec(SecureUtil.decode(priKey)));
final ECPrivateKeyParameters privateKeyParameters = ECKeyUtil.toPrivateParams(privateKey);
final SM2 sm2 = new SM2(privateKeyParameters, ECKeyUtil.getPublicParams(privateKeyParameters));
String src = "Sm2Test";
byte[] data = sm2.encrypt(src, KeyType.PublicKey);
byte[] sign = sm2.sign(src.getBytes(StandardCharsets.UTF_8));
Assert.assertTrue(sm2.verify( src.getBytes(StandardCharsets.UTF_8), sign));
byte[] dec = sm2.decrypt(data, KeyType.PrivateKey);
Assert.assertArrayEquals(dec, src.getBytes(StandardCharsets.UTF_8));
}
@Test
public void readPublicKeyTest(){
String priKey = "MHcCAQEEIE29XqAFV/rkJbnJzCoQRJLTeAHG2TR0h9ZCWag0+ZMEoAoGCCqBHM9VAYItoUQDQgAESkOzNigIsH5ehFvr9y" +
"QNQ66genyOrm+Q4umCA4aWXPeRzmcTAWSlTineiReTFN2lqor2xaulT8u3a4w3AM/F6A==";
String pubKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAESkOzNigIsH5ehFvr9yQNQ66genyOrm+Q4umCA4aWXPeRzmcTAWSlTineiReTFN2lqor2xaulT8u3a4w3AM/F6A==";
SM2 sm2 = SmUtil.sm2(priKey, pubKey);
String src = "Sm2Test中文";
byte[] data = sm2.encrypt(src, KeyType.PublicKey);
byte[] sign = sm2.sign(src.getBytes(StandardCharsets.UTF_8));
Assert.assertTrue(sm2.verify( src.getBytes(StandardCharsets.UTF_8), sign));
byte[] dec = sm2.decrypt(data, KeyType.PrivateKey);
Assert.assertArrayEquals(dec, src.getBytes(StandardCharsets.UTF_8));
}
}