mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge branch 'v5-master' into v5-dev
This commit is contained in:
commit
db26085379
@ -34,5 +34,12 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
|
<version>${bouncycastle.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -2,20 +2,27 @@ package cn.hutool.crypto;
|
|||||||
|
|
||||||
import cn.hutool.core.io.IORuntimeException;
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||||
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
|
import org.bouncycastle.openssl.*;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
|
||||||
|
import org.bouncycastle.operator.InputDecryptorProvider;
|
||||||
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||||
|
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
||||||
|
import org.bouncycastle.pkcs.PKCSException;
|
||||||
import org.bouncycastle.util.io.pem.PemObject;
|
import org.bouncycastle.util.io.pem.PemObject;
|
||||||
import org.bouncycastle.util.io.pem.PemObjectGenerator;
|
import org.bouncycastle.util.io.pem.PemObjectGenerator;
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
import org.bouncycastle.util.io.pem.PemWriter;
|
import org.bouncycastle.util.io.pem.PemWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.*;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
import java.security.Provider;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,6 +49,18 @@ public class PemUtil {
|
|||||||
return (PrivateKey) readPemKey(pemStream);
|
return (PrivateKey) readPemKey(pemStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取加密的 PEM 格式私钥
|
||||||
|
*
|
||||||
|
* @param pemStream pem 流
|
||||||
|
* @param password 私钥的密码
|
||||||
|
* @return {@link PrivateKey}
|
||||||
|
* @since 5.8.4
|
||||||
|
*/
|
||||||
|
public static PrivateKey readPemPrivateKey(InputStream pemStream, final char[] password) {
|
||||||
|
return (PrivateKey) readPemKey(pemStream, password);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取PEM格式的公钥
|
* 读取PEM格式的公钥
|
||||||
*
|
*
|
||||||
@ -62,30 +81,64 @@ public class PemUtil {
|
|||||||
* @since 5.1.6
|
* @since 5.1.6
|
||||||
*/
|
*/
|
||||||
public static Key readPemKey(InputStream keyStream) {
|
public static Key readPemKey(InputStream keyStream) {
|
||||||
final PemObject object = readPemObject(keyStream);
|
return readPemKey(keyStream, null);
|
||||||
final String type = object.getType();
|
|
||||||
if (StrUtil.isNotBlank(type)) {
|
|
||||||
//private
|
|
||||||
if (type.endsWith("EC PRIVATE KEY")) {
|
|
||||||
return KeyUtil.generatePrivateKey("EC", object.getContent());
|
|
||||||
}
|
|
||||||
if (type.endsWith("PRIVATE KEY")) {
|
|
||||||
return KeyUtil.generateRSAPrivateKey(object.getContent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// public
|
/**
|
||||||
if (type.endsWith("EC PUBLIC KEY")) {
|
* 从pem文件中读取公钥或私钥<br/>
|
||||||
return KeyUtil.generatePublicKey("EC", object.getContent());
|
* 根据类型返回 {@link PublicKey} 或者 {@link PrivateKey}
|
||||||
} else if (type.endsWith("PUBLIC KEY")) {
|
*
|
||||||
return KeyUtil.generateRSAPublicKey(object.getContent());
|
* @param keyStream pem 流
|
||||||
} else if (type.endsWith("CERTIFICATE")) {
|
* @param password 私钥密码
|
||||||
return KeyUtil.readPublicKeyFromCert(IoUtil.toStream(object.getContent()));
|
* @return {@link Key},null 表示无法识别的密钥类型
|
||||||
}
|
* @since 5.8.4
|
||||||
}
|
*/
|
||||||
|
public static Key readPemKey(InputStream keyStream, final char[] password) {
|
||||||
|
|
||||||
|
final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider();
|
||||||
|
|
||||||
|
try (PEMParser pemParser = new PEMParser(new InputStreamReader(keyStream))) {
|
||||||
|
|
||||||
|
Object keyObject = pemParser.readObject();
|
||||||
|
|
||||||
|
JcaPEMKeyConverter pemKeyConverter = new JcaPEMKeyConverter().setProvider(provider);
|
||||||
|
|
||||||
|
if (keyObject instanceof PrivateKeyInfo) {
|
||||||
|
// PrivateKeyInfo
|
||||||
|
return pemKeyConverter.getPrivateKey((PrivateKeyInfo) keyObject);
|
||||||
|
} else if (keyObject instanceof PEMKeyPair) {
|
||||||
|
// PemKeyPair
|
||||||
|
return pemKeyConverter.getKeyPair((PEMKeyPair) keyObject).getPrivate();
|
||||||
|
} else if (keyObject instanceof PKCS8EncryptedPrivateKeyInfo) {
|
||||||
|
// Encrypted PrivateKeyInfo
|
||||||
|
InputDecryptorProvider decryptProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(provider).build(password);
|
||||||
|
PrivateKeyInfo privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) keyObject).decryptPrivateKeyInfo(decryptProvider);
|
||||||
|
return pemKeyConverter.getPrivateKey(privateKeyInfo);
|
||||||
|
} else if (keyObject instanceof PEMEncryptedKeyPair) {
|
||||||
|
// Encrypted PemKeyPair
|
||||||
|
PEMDecryptorProvider decryptProvider = new JcePEMDecryptorProviderBuilder().setProvider(provider).build(password);
|
||||||
|
PrivateKeyInfo privateKeyInfo = ((PEMEncryptedKeyPair) keyObject).decryptKeyPair(decryptProvider).getPrivateKeyInfo();
|
||||||
|
return pemKeyConverter.getPrivateKey(privateKeyInfo);
|
||||||
|
} else if (keyObject instanceof SubjectPublicKeyInfo) {
|
||||||
|
// SubjectPublicKeyInfo
|
||||||
|
return pemKeyConverter.getPublicKey((SubjectPublicKeyInfo) keyObject);
|
||||||
|
} else if (keyObject instanceof X509CertificateHolder) {
|
||||||
|
// X509 Certificate
|
||||||
|
return pemKeyConverter.getPublicKey(((X509CertificateHolder) keyObject).getSubjectPublicKeyInfo());
|
||||||
|
} else if (keyObject instanceof X509TrustedCertificateBlock) {
|
||||||
|
// X509 Trusted Certificate
|
||||||
|
return pemKeyConverter.getPublicKey(((X509TrustedCertificateBlock) keyObject).getCertificateHolder().getSubjectPublicKeyInfo());
|
||||||
|
} else if (keyObject instanceof PKCS10CertificationRequest) {
|
||||||
|
// PKCS#10 CSR
|
||||||
|
return pemKeyConverter.getPublicKey(((PKCS10CertificationRequest) keyObject).getSubjectPublicKeyInfo());
|
||||||
|
} else {
|
||||||
// 表示无法识别的密钥类型
|
// 表示无法识别的密钥类型
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
} catch (IOException | OperatorCreationException | PKCSException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从pem流中读取公钥或私钥
|
* 从pem流中读取公钥或私钥
|
||||||
@ -139,15 +192,12 @@ public class PemUtil {
|
|||||||
* @return {@link PrivateKey}
|
* @return {@link PrivateKey}
|
||||||
*/
|
*/
|
||||||
public static PrivateKey readSm2PemPrivateKey(InputStream keyStream) {
|
public static PrivateKey readSm2PemPrivateKey(InputStream keyStream) {
|
||||||
try{
|
return readPemPrivateKey(keyStream);
|
||||||
return KeyUtil.generatePrivateKey("sm2", ECKeyUtil.createOpenSSHPrivateKeySpec(readPem(keyStream)));
|
|
||||||
} finally {
|
|
||||||
IoUtil.close(keyStream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将私钥或公钥转换为PEM格式的字符串
|
* 将私钥或公钥转换为PEM格式的字符串
|
||||||
|
*
|
||||||
* @param type 密钥类型(私钥、公钥、证书)
|
* @param type 密钥类型(私钥、公钥、证书)
|
||||||
* @param content 密钥内容
|
* @param content 密钥内容
|
||||||
* @return PEM内容
|
* @return PEM内容
|
||||||
|
@ -14,6 +14,7 @@ import org.junit.Test;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public class PemUtilTest {
|
public class PemUtilTest {
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ public class PemUtilTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readECPrivateKeyTest() {
|
public void readECPrivateKeyTest() {
|
||||||
PrivateKey privateKey = PemUtil.readSm2PemPrivateKey(ResourceUtil.getStream("test_ec_private_key.pem"));
|
PrivateKey privateKey = PemUtil.readSm2PemPrivateKey(ResourceUtil.getStream("test_ec_sec1_private_key.pem"));
|
||||||
SM2 sm2 = new SM2(privateKey, null);
|
SM2 sm2 = new SM2(privateKey, null);
|
||||||
sm2.usePlainEncoding();
|
sm2.usePlainEncoding();
|
||||||
|
|
||||||
@ -77,4 +78,46 @@ public class PemUtilTest {
|
|||||||
boolean verify = sm2.verify(StrUtil.utf8Bytes(content), sign);
|
boolean verify = sm2.verify(StrUtil.utf8Bytes(content), sign);
|
||||||
Assert.assertTrue(verify);
|
Assert.assertTrue(verify);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyPemUtilReadKey() {
|
||||||
|
// 公钥
|
||||||
|
// PKCS#10 文件读取公钥
|
||||||
|
PublicKey csrPublicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_ec_certificate_request.csr"));
|
||||||
|
|
||||||
|
// 证书读取公钥
|
||||||
|
PublicKey certPublicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_ec_certificate.cer"));
|
||||||
|
|
||||||
|
// PEM 公钥
|
||||||
|
PublicKey plainPublicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_ec_public_key.pem"));
|
||||||
|
|
||||||
|
// 私钥
|
||||||
|
// 加密的 PEM 私钥
|
||||||
|
PrivateKey encPrivateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_ec_encrypted_private_key.key"), "123456".toCharArray());
|
||||||
|
|
||||||
|
// PKCS#8 私钥
|
||||||
|
PrivateKey pkcs8PrivateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_ec_pkcs8_private_key.key"));
|
||||||
|
|
||||||
|
// SEC 1 私钥
|
||||||
|
PrivateKey sec1PrivateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_ec_sec1_private_key.pem"));
|
||||||
|
|
||||||
|
// 组装还原后的公钥和私钥列表
|
||||||
|
List<PublicKey> publicKeyList = Arrays.asList(csrPublicKey, certPublicKey, plainPublicKey);
|
||||||
|
List<PrivateKey> privateKeyList = Arrays.asList(encPrivateKey, pkcs8PrivateKey, sec1PrivateKey);
|
||||||
|
|
||||||
|
// 做笛卡尔积循环验证
|
||||||
|
for (PrivateKey privateKeyItem : privateKeyList) {
|
||||||
|
for (PublicKey publicKeyItem : publicKeyList) {
|
||||||
|
// 校验公私钥
|
||||||
|
SM2 genSm2 = new SM2(privateKeyItem, publicKeyItem);
|
||||||
|
genSm2.usePlainEncoding();
|
||||||
|
|
||||||
|
String content = "我是Hanley.";
|
||||||
|
byte[] sign = genSm2.sign(StrUtil.utf8Bytes(content));
|
||||||
|
boolean verify = genSm2.verify(StrUtil.utf8Bytes(content), sign);
|
||||||
|
Assert.assertTrue(verify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
11
hutool-crypto/src/test/resources/test_ec_certificate.cer
Normal file
11
hutool-crypto/src/test/resources/test_ec_certificate.cer
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBozCCAUegAwIBAgIIJQJK8f5oQVQwDAYIKoEcz1UBg3UFADAjMQswCQYDVQQG
|
||||||
|
EwJDTjEUMBIGA1UEAwwLSHV0b29sIFRlc3QwHhcNMjIwNzE3MDcyMzU4WhcNMjMw
|
||||||
|
NzE3MDcyMzU4WjAjMQswCQYDVQQGEwJDTjEUMBIGA1UEAwwLSHV0b29sIFRlc3Qw
|
||||||
|
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXXjbO+6vY7vdD5aXoi2EMHUq0itI8
|
||||||
|
kG6FN3cgLBFFoelyy3JxX94h7RpH4ylpNUXeRNuzv1VcPa06nsN1OjTWo2MwYTAP
|
||||||
|
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUFHvMM9CZ
|
||||||
|
VUxWKt1+CHI5uYLpLgQwHwYDVR0jBBgwFoAUFHvMM9CZVUxWKt1+CHI5uYLpLgQw
|
||||||
|
DAYIKoEcz1UBg3UFAANIADBFAiA5p0Gh7mvuuHMVG2SmbGj5HQpWcpwCaUF90BQ9
|
||||||
|
/QEYZgIhAIXmeD2bDlOCPc3vxS4aNGxSd1wq/MT9bBJhKyiXWjx5
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIBODCB3wIBADBWMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHQmVpamluZzEQMA4G
|
||||||
|
A1UEBxMHQmVpamluZzEPMA0GA1UECxMGSHV0b29sMRIwEAYDVQQDEwlodXRvb2wu
|
||||||
|
Y24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXXjbO+6vY7vdD5aXoi2EMHUq0
|
||||||
|
itI8kG6FN3cgLBFFoelyy3JxX94h7RpH4ylpNUXeRNuzv1VcPa06nsN1OjTWoCcw
|
||||||
|
JQYJKoZIhvcNAQkOMRgwFjAUBgNVHREEDTALgglodXRvb2wuY24wCgYIKoZIzj0E
|
||||||
|
AwIDSAAwRQIhAIH0w1XbGnRfbM1flsqRHzfur+pEZ3wiqpChxAI39lpIAiAsNAwn
|
||||||
|
o7PsXpTXLhq1ZgqurIjFeFuvY1hszUODmO5ySQ==
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
@ -0,0 +1,8 @@
|
|||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
Proc-Type: 4,ENCRYPTED
|
||||||
|
DEK-Info: AES-256-CBC,04b396b78c1f1172857a8b76682b63ef
|
||||||
|
|
||||||
|
NoaLaSy87gLE6C3yCi7JwiB86NSYipZmMVlLIcHaBL2ECRUcGDmXEZu6OqFyrbDc
|
||||||
|
XWXraEl3OieYduiVmuJ0GQ8oeWd5DNgHLBYTPnfgjBowbluAO9/R9AUh4R8Fz918
|
||||||
|
/zsMZJckjSv3Gs6NWZW02v9OvhTDSJBNwu/M2WTWH10=
|
||||||
|
-----END EC PRIVATE KEY-----
|
@ -0,0 +1,6 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgEZmc6NdYu8UzpzhX
|
||||||
|
1bcRzfWBlcGgnPtqfVsRzXfXZ/6gCgYIKoZIzj0DAQehRANCAASXXjbO+6vY7vdD
|
||||||
|
5aXoi2EMHUq0itI8kG6FN3cgLBFFoelyy3JxX94h7RpH4ylpNUXeRNuzv1VcPa06
|
||||||
|
nsN1OjTW
|
||||||
|
-----END PRIVATE KEY-----
|
@ -1,5 +0,0 @@
|
|||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
MHcCAQEEIKB89IhhSy9WrtQS7TWO5Yqyv5a3DnogWYUhb3TbzjnWoAoGCCqBHM9V
|
|
||||||
AYItoUQDQgAE3LRuqCM697gL3jPhw98eGfTDcJsuJr6H1nE4VkgdtBdX3So2lC6m
|
|
||||||
UGEnWeRZuh8HnzCRobcu02Bgv7CVR5Iigg==
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
4
hutool-crypto/src/test/resources/test_ec_public_key.pem
Normal file
4
hutool-crypto/src/test/resources/test_ec_public_key.pem
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl142zvur2O73Q+Wl6IthDB1KtIrS
|
||||||
|
PJBuhTd3ICwRRaHpcstycV/eIe0aR+MpaTVF3kTbs79VXD2tOp7DdTo01g==
|
||||||
|
-----END PUBLIC KEY-----
|
@ -0,0 +1,5 @@
|
|||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHcCAQEEIBGZnOjXWLvFM6c4V9W3Ec31gZXBoJz7an1bEc1312f+oAoGCCqGSM49
|
||||||
|
AwEHoUQDQgAEl142zvur2O73Q+Wl6IthDB1KtIrSPJBuhTd3ICwRRaHpcstycV/e
|
||||||
|
Ie0aR+MpaTVF3kTbs79VXD2tOp7DdTo01g==
|
||||||
|
-----END EC PRIVATE KEY-----
|
Loading…
x
Reference in New Issue
Block a user