From 7c2e0ed887cd5d218b459072cc8335cfdb815f8a Mon Sep 17 00:00:00 2001 From: changhr2013 Date: Sun, 17 Jul 2022 15:41:26 +0800 Subject: [PATCH] [new] enhance PemUtil#readPemKey method, support more pem format, depend on bcpkix-jdk15on --- hutool-crypto/pom.xml | 7 ++ .../main/java/cn/hutool/crypto/PemUtil.java | 116 +++++++++++++----- .../cn/hutool/crypto/test/PemUtilTest.java | 45 ++++++- .../test/resources/test_ec_certificate.cer | 11 ++ .../resources/test_ec_certificate_request.csr | 9 ++ .../test_ec_encrypted_private_key.key | 8 ++ .../resources/test_ec_pkcs8_private_key.key | 6 + .../test/resources/test_ec_private_key.pem | 5 - .../src/test/resources/test_ec_public_key.pem | 4 + .../resources/test_ec_sec1_private_key.pem | 5 + 10 files changed, 177 insertions(+), 39 deletions(-) create mode 100644 hutool-crypto/src/test/resources/test_ec_certificate.cer create mode 100644 hutool-crypto/src/test/resources/test_ec_certificate_request.csr create mode 100644 hutool-crypto/src/test/resources/test_ec_encrypted_private_key.key create mode 100644 hutool-crypto/src/test/resources/test_ec_pkcs8_private_key.key delete mode 100644 hutool-crypto/src/test/resources/test_ec_private_key.pem create mode 100644 hutool-crypto/src/test/resources/test_ec_public_key.pem create mode 100644 hutool-crypto/src/test/resources/test_ec_sec1_private_key.pem diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 800f5b781..73c78bf20 100755 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -34,5 +34,12 @@ compile true + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + compile + true + diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java index c7ca4205b..180cf4186 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java @@ -2,20 +2,27 @@ 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.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.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.io.StringWriter; -import java.io.Writer; +import java.io.*; import java.security.Key; import java.security.PrivateKey; +import java.security.Provider; import java.security.PublicKey; /** @@ -42,6 +49,18 @@ public class PemUtil { 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格式的公钥 * @@ -62,29 +81,63 @@ public class PemUtil { * @since 5.1.6 */ public static Key readPemKey(InputStream keyStream) { - final PemObject object = readPemObject(keyStream); - 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()); - } + return readPemKey(keyStream, null); + } - // public - if (type.endsWith("EC PUBLIC KEY")) { - return KeyUtil.generatePublicKey("EC", 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())); + /** + * 从pem文件中读取公钥或私钥
+ * 根据类型返回 {@link PublicKey} 或者 {@link PrivateKey} + * + * @param keyStream pem 流 + * @param password 私钥密码 + * @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; } + } catch (IOException | OperatorCreationException | PKCSException e) { + throw new RuntimeException(e); } - - //表示无法识别的密钥类型 - return null; } /** @@ -139,16 +192,13 @@ public class PemUtil { * @return {@link PrivateKey} */ public static PrivateKey readSm2PemPrivateKey(InputStream keyStream) { - try{ - return KeyUtil.generatePrivateKey("sm2", ECKeyUtil.createOpenSSHPrivateKeySpec(readPem(keyStream))); - } finally { - IoUtil.close(keyStream); - } + return readPemPrivateKey(keyStream); } /** * 将私钥或公钥转换为PEM格式的字符串 - * @param type 密钥类型(私钥、公钥、证书) + * + * @param type 密钥类型(私钥、公钥、证书) * @param content 密钥内容 * @return PEM内容 * @since 5.5.9 diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java index 0daea2e31..f6541d5bd 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java @@ -14,6 +14,7 @@ import org.junit.Test; import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.PublicKey; +import java.util.*; public class PemUtilTest { @@ -50,7 +51,7 @@ public class PemUtilTest { @Test 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.usePlainEncoding(); @@ -77,4 +78,46 @@ public class PemUtilTest { boolean verify = sm2.verify(StrUtil.utf8Bytes(content), sign); 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 publicKeyList = Arrays.asList(csrPublicKey, certPublicKey, plainPublicKey); + List 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); + } + } + } + } diff --git a/hutool-crypto/src/test/resources/test_ec_certificate.cer b/hutool-crypto/src/test/resources/test_ec_certificate.cer new file mode 100644 index 000000000..fcb41fa7a --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_certificate.cer @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBozCCAUegAwIBAgIIJQJK8f5oQVQwDAYIKoEcz1UBg3UFADAjMQswCQYDVQQG +EwJDTjEUMBIGA1UEAwwLSHV0b29sIFRlc3QwHhcNMjIwNzE3MDcyMzU4WhcNMjMw +NzE3MDcyMzU4WjAjMQswCQYDVQQGEwJDTjEUMBIGA1UEAwwLSHV0b29sIFRlc3Qw +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXXjbO+6vY7vdD5aXoi2EMHUq0itI8 +kG6FN3cgLBFFoelyy3JxX94h7RpH4ylpNUXeRNuzv1VcPa06nsN1OjTWo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUFHvMM9CZ +VUxWKt1+CHI5uYLpLgQwHwYDVR0jBBgwFoAUFHvMM9CZVUxWKt1+CHI5uYLpLgQw +DAYIKoEcz1UBg3UFAANIADBFAiA5p0Gh7mvuuHMVG2SmbGj5HQpWcpwCaUF90BQ9 +/QEYZgIhAIXmeD2bDlOCPc3vxS4aNGxSd1wq/MT9bBJhKyiXWjx5 +-----END CERTIFICATE----- diff --git a/hutool-crypto/src/test/resources/test_ec_certificate_request.csr b/hutool-crypto/src/test/resources/test_ec_certificate_request.csr new file mode 100644 index 000000000..458056676 --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_certificate_request.csr @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBODCB3wIBADBWMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHQmVpamluZzEQMA4G +A1UEBxMHQmVpamluZzEPMA0GA1UECxMGSHV0b29sMRIwEAYDVQQDEwlodXRvb2wu +Y24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXXjbO+6vY7vdD5aXoi2EMHUq0 +itI8kG6FN3cgLBFFoelyy3JxX94h7RpH4ylpNUXeRNuzv1VcPa06nsN1OjTWoCcw +JQYJKoZIhvcNAQkOMRgwFjAUBgNVHREEDTALgglodXRvb2wuY24wCgYIKoZIzj0E +AwIDSAAwRQIhAIH0w1XbGnRfbM1flsqRHzfur+pEZ3wiqpChxAI39lpIAiAsNAwn +o7PsXpTXLhq1ZgqurIjFeFuvY1hszUODmO5ySQ== +-----END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/hutool-crypto/src/test/resources/test_ec_encrypted_private_key.key b/hutool-crypto/src/test/resources/test_ec_encrypted_private_key.key new file mode 100644 index 000000000..dd3b2351f --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_encrypted_private_key.key @@ -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----- diff --git a/hutool-crypto/src/test/resources/test_ec_pkcs8_private_key.key b/hutool-crypto/src/test/resources/test_ec_pkcs8_private_key.key new file mode 100644 index 000000000..e4b404e93 --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_pkcs8_private_key.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgEZmc6NdYu8UzpzhX +1bcRzfWBlcGgnPtqfVsRzXfXZ/6gCgYIKoZIzj0DAQehRANCAASXXjbO+6vY7vdD +5aXoi2EMHUq0itI8kG6FN3cgLBFFoelyy3JxX94h7RpH4ylpNUXeRNuzv1VcPa06 +nsN1OjTW +-----END PRIVATE KEY----- diff --git a/hutool-crypto/src/test/resources/test_ec_private_key.pem b/hutool-crypto/src/test/resources/test_ec_private_key.pem deleted file mode 100644 index 8ae4ed315..000000000 --- a/hutool-crypto/src/test/resources/test_ec_private_key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIKB89IhhSy9WrtQS7TWO5Yqyv5a3DnogWYUhb3TbzjnWoAoGCCqBHM9V -AYItoUQDQgAE3LRuqCM697gL3jPhw98eGfTDcJsuJr6H1nE4VkgdtBdX3So2lC6m -UGEnWeRZuh8HnzCRobcu02Bgv7CVR5Iigg== ------END EC PRIVATE KEY----- diff --git a/hutool-crypto/src/test/resources/test_ec_public_key.pem b/hutool-crypto/src/test/resources/test_ec_public_key.pem new file mode 100644 index 000000000..692d47b9f --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_public_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl142zvur2O73Q+Wl6IthDB1KtIrS +PJBuhTd3ICwRRaHpcstycV/eIe0aR+MpaTVF3kTbs79VXD2tOp7DdTo01g== +-----END PUBLIC KEY----- diff --git a/hutool-crypto/src/test/resources/test_ec_sec1_private_key.pem b/hutool-crypto/src/test/resources/test_ec_sec1_private_key.pem new file mode 100644 index 000000000..8b0b28be9 --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_sec1_private_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBGZnOjXWLvFM6c4V9W3Ec31gZXBoJz7an1bEc1312f+oAoGCCqGSM49 +AwEHoUQDQgAEl142zvur2O73Q+Wl6IthDB1KtIrSPJBuhTd3ICwRRaHpcstycV/e +Ie0aR+MpaTVF3kTbs79VXD2tOp7DdTo01g== +-----END EC PRIVATE KEY-----