我正在尝试从私钥获取公钥,对于某些用例,如果类型是实例
PEMKeyPair
和PEMEncryptedKeyPair
,则很简单。这两个可以很容易地用 java.security.KeyPair
转换为 JcaPEMKeyConverter
。但是,如果我有 PrivateKeyInfo
和 PKCS8EncryptedPrivateKeyInfo
类型的对象,我只能使用 JcaPEMKeyConverter
提取私钥,并且我不确定是否有可用的实用程序或更好的方法来从中提取公钥它。目前我正在使用一种解决方法,该方法效率不高,因为我创建了一个 pem 文件,然后使用 pemparser 重新读取它以提取公钥,这发生在 extractPublicKey
方法中。请参阅下面的完整代码片段:
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
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.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class App {
private static final JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter();
private static final JceOpenSSLPKCS8DecryptorProviderBuilder pkcs8DecryptorProviderBuilder = new JceOpenSSLPKCS8DecryptorProviderBuilder();
private static final JcePEMDecryptorProviderBuilder pemDecryptorProviderBuilder = new JcePEMDecryptorProviderBuilder();
public static void main(String[] args) {
Security.addProvider(new BouncyCastleProvider());
List<Object> objects = parsePemContent(PEM_CONTENT);
Object object = objects.get(0);
KeyPair keyPair = extractKeyPair(object, null).orElseThrow();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
}
private static List<Object> parsePemContent(String pemContent) {
try (Reader stringReader = new StringReader(pemContent);
PEMParser pemParser = new PEMParser(stringReader)) {
List<Object> objects = new ArrayList<>();
for (Object object = pemParser.readObject(); object != null; object = pemParser.readObject()) {
objects.add(object);
}
return objects;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static Optional<KeyPair> extractKeyPair(Object object, char[] keyPassword) {
try {
PrivateKeyInfo privateKeyInfo = null;
PEMKeyPair pemKeyPair = null;
KeyPair keyPair = null;
if (object instanceof PrivateKeyInfo) {
privateKeyInfo = (PrivateKeyInfo) object;
} else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
InputDecryptorProvider decryptorProvider = pkcs8DecryptorProviderBuilder.build(keyPassword);
PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) object;
privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo(decryptorProvider);
} else if (object instanceof PEMKeyPair) {
pemKeyPair = (PEMKeyPair) object;
} else if (object instanceof PEMEncryptedKeyPair) {
PEMDecryptorProvider decryptorProvider = pemDecryptorProviderBuilder.build(keyPassword);
PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) object;
pemKeyPair = encryptedKeyPair.decryptKeyPair(decryptorProvider);
}
if (privateKeyInfo != null) {
PrivateKey privateKey = keyConverter.getPrivateKey(privateKeyInfo);
PublicKey publicKey = extractPublicKey(privateKey);
keyPair = new KeyPair(publicKey, privateKey);
}
if (pemKeyPair != null) {
keyPair = keyConverter.getKeyPair(pemKeyPair);
}
return Optional.ofNullable(keyPair);
} catch (IOException | OperatorCreationException | PKCSException e) {
throw new RuntimeException(e);
}
}
private static PublicKey extractPublicKey(PrivateKey privateKey) {
try (StringWriter writer = new StringWriter()) {
JcaMiscPEMGenerator pemGenerator = new JcaMiscPEMGenerator(privateKey);
PemObject pemObject = pemGenerator.generate();
PemWriter pemWriter = new PemWriter(writer);
pemWriter.writeObject(pemObject);
pemWriter.close();
try (StringReader stringReader = new StringReader(writer.toString());
PEMParser pemParser = new PEMParser(stringReader)) {
PEMKeyPair pemKeyPair = (PEMKeyPair) pemParser.readObject();
KeyPair keyPair = keyConverter.getKeyPair(pemKeyPair);
return keyPair.getPublic();
}
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
private static final String PEM_CONTENT = """
Bag Attributes
friendlyName: client
localKeyID: 54 69 6D 65 20 31 35 39 39 34 30 31 38 32 38 37 32 35\s
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCTBHn+btuggNTb
B5eOMTSpKnIL6+GTObUkn6PdTSV8Ids65tJCRWBOKARNCbJsRCfJeHdtvFojYZXm
r6PKi84CsYPafn/D/8rIJCvbvUFM1K+zB7MpF3UzRhyU3QqsTfdutr72nLhUXlzl
eH6bNJ547N8hz8NpU48jMNz7xB4MlekjrhlLHtgq8Qb43Q+lZm2VUW8iGC5xoDK1
SnPlm5AoWVCaRbaN6nmfoeSIAdlzixtO41ZYGQm1Blhzsk08fQ4H5I6zHKYKyHY8
k5oKpE7+wB7fGM0qyI19wAy1UuEa5hvSilrM66GoHkQ5bEa78QeD5PHw5ohofpKh
Vys+vIFVAgMBAAECggEAR0OsPwFNxQeuJl4PwQVpGXdRwSWeOteGTzJzJBr5SKrA
slShJy6p+Di9nPpOWtzOzIJwoejjaLMtDp2lL9GFExkpaQhYtpGPomSmPeYHeU6/
vHDHD+wnC6u4vxBG1C8W+bvr5W8iiwMS1MkL1gAzsTphDuq/Npcik1RkSkZOqppj
t6kQVxJ7yJ+iSPFzubABv4luxLg30ESSw/vD8MfaNWcud3XE5YAKACjqQqi2IEkE
IJ/sCrqAq71bdlsFkAwm2T/8ecIBoUKuCYS9gteVAMXfT0lV5cAIag+zZ5cyOiBL
Nx8UW3J5tc06u8mcYEIBQsWtrmNOkaV6ntFgG0r+IQKBgQDR182z6pQkVBPdHSI1
V7gITifOmxZEUBWSnb3ekrHtCNcUzQ62Cd+0mCPcnhB40nm+7PfpOVsVu7gJEZb7
e6xT8uYm4ZkFQqNzZQcx8TWXHEeaAArwzMetDrgz+bwZlWcUcG7S3RMybvWPqScz
AXjYNGriZScHW8yh0CEUDsyVGwKBgQCzWvzj/W7D3d0C2dSJoSLsyseFd0kdUHA7
1zj4PqOqfpZAdEe6elUKRADx1lIi/YVd2h5h4aN+dsZ71feCJLnZkCLIkeWn2M6Q
5O9vtbmQ8YhxGktiqP7km3KgAdZk4C53Tacjq3R37JSOEX3vnJHUoXzkxXA71g7/
SassCX1aTwKBgQCXoeZ9tOuZmLvF0rCOdTWBouA29nBPqsL78EpsU/qIOxQYbtjL
iDUDrdB0Mi/a7tSUt22pNQ3xlXU18GT2knaDLwlKXUiSuYWc9AsP9qnv6LqAuLkv
Kfq7veAzhql6nzAeX+RlMOUXU4DUb7norI6jRLVbpRZfxeEHqHrOoKcKswKBgBV4
4SnSX35ng1wiBAXuGqZKqJRb8Y7m4GjpnVJq/WEeApL42NWEa8Xs2kgZpn+15k+U
G2sQfmhXg++zcAxOpUlcri1g+iOcGy7RmbDACtVFdVZFFZ1cKhfoXFK3pZkyFZ4G
1+m3TxxEYIyZn4AeOH9CThd9Y7BmMilyAmIlSLKVAoGADbX0WVnQieiA2NdgUqmX
QIcg3Hsspsx5Ro7EReYuHVAC2J7WOrnINLPsCU+jLdnDm4iXgvjdb9Lsx3qCyAUA
+K9GqpcRYXRGTz7+YdFVneU19WK6Jeo5vmy66KXVGBk/134aQ0QKQ1Tfnd+fmAtV
iVDJeabSN63mDyr6CX6j5/0=
-----END PRIVATE KEY-----
Bag Attributes
friendlyName: client
localKeyID: 54 69 6D 65 20 31 35 39 39 34 30 31 38 32 38 37 32 35\s
subject=/C=NL/O=Altindag/OU=Altindag/CN=black-hole
issuer=/C=NL/O=Thunderberry/OU=Certificate Authority/CN=Root-CA
-----BEGIN CERTIFICATE-----
MIIDGjCCAgICCQDLR+kGVrMOoTANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJO
TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB
dXRob3JpdHkxEDAOBgNVBAMTB1Jvb3QtQ0EwHhcNMjAwOTA2MDg0MTI0WhcNMjUw
OTA1MDg0MTI0WjBIMQswCQYDVQQGEwJOTDERMA8GA1UEChMIQWx0aW5kYWcxETAP
BgNVBAsTCEFsdGluZGFnMRMwEQYDVQQDEwpibGFjay1ob2xlMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkwR5/m7boIDU2weXjjE0qSpyC+vhkzm1JJ+j
3U0lfCHbOubSQkVgTigETQmybEQnyXh3bbxaI2GV5q+jyovOArGD2n5/w//KyCQr
271BTNSvswezKRd1M0YclN0KrE33bra+9py4VF5c5Xh+mzSeeOzfIc/DaVOPIzDc
+8QeDJXpI64ZSx7YKvEG+N0PpWZtlVFvIhgucaAytUpz5ZuQKFlQmkW2jep5n6Hk
iAHZc4sbTuNWWBkJtQZYc7JNPH0OB+SOsxymCsh2PJOaCqRO/sAe3xjNKsiNfcAM
tVLhGuYb0opazOuhqB5EOWxGu/EHg+Tx8OaIaH6SoVcrPryBVQIDAQABMA0GCSqG
SIb3DQEBBQUAA4IBAQCWrAt3mlE+6iT8N55rFPqgOLBcdXizSQqW2ycMvwau9FqL
V5i6nHDV6jWoGIuOmo4IvkXWBN/Q+SPqoAeSvJw5WgVpd6CF1+QlcdpP9k3utJqK
OZJduZxgqTYHw+QUQbDCGHlVJCQZKKHqKQryXdP18SAXQt0GrDkmE53FUUF9Act3
NxDUiMf3AgbS6NxY9oH3RR5LvcyvM8FYHaGX1mjjQzY8Fr0TmR+x8ItrXDphN0MW
J3EuJE2qz7Dx7CQgJAiaQ43o7qMuVfqzKqSv8Sk46OBQCss0VNpqfsUCq0niIJJB
NNPJMUaSaIx0aKdbvoEAVeekkGMBWakVW4z0xNMK
-----END CERTIFICATE-----
""";
}
我使用了 Java 17 和以下充气城堡依赖项:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.76</version>
</dependency>
知道是否可以从
PrivateKeyInfo
中提取公钥,而不需要使用方法 extractPublicKey
中的解决方法,该方法暂时使用 JcaMiscPEMGenerator
创建私钥的 pem 字符串,以便使用 PEMParser 重新处理获得 PEMKeyPair
,而它又持有私钥和公钥。
为什么它对 Ed25519 不起作用
[Jca]MiscPEMGenerator
为 RSA、DSA 和 EC(DSA) 生成 OpenSSL“传统”格式,其中 PEMParser
根据需要读回 PEMKeyPair
类型,但对于包括 Ed25519 在内的其他算法,它会生成 PKCS8 格式 --与您开始使用的格式相同(除了未加密,即使您的输入已加密),它会读回 PrivateKeyInfo
,但什么也没完成。
你能做什么
对于 Ed25519,JCA API 实际上允许您直接生成公钥。其他算法则不然,但对于 RSA,在实践中总是使用的“CRT”子 API 包含所需的信息,而对于 EC,实践中的 PKCS8 格式1 包含所需信息。仅对于 DSA(您没有要求但我出于接近完整性而将其包括在内),您是否需要不安全的破解。 (我没有包含 DH,因为实际上 DH 不与存储在任何地方的长期密钥一起使用。)
1 PKCS8 中使用的 SEC1 ECPrivateKey 格式 正式具有
publicKey
字段可选,但我从未遇到过不生成它的软件。
PublicKey publicKey = null;
if( privateKey instanceof RSAPrivateCrtKey )
publicKey = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(
((RSAPrivateCrtKey)privateKey).getModulus(), ((RSAPrivateCrtKey)privateKey).getPublicExponent() ));
else if( privateKey instanceof DSAPrivateKey ){
DSAParams dsap = ((DSAPrivateKey)privateKey).getParams();
publicKey = KeyFactory.getInstance("DSA").generatePublic(new DSAPublicKeySpec(
dsap.getG().modPow( ((DSAPrivateKey)privateKey).getX(), dsap.getP()), dsap.getP(), dsap.getQ(), dsap.getG() ));
// WARNING! VULNERABLE TO SIDE-CHANNEL ATTACKS!
}else if( privateKey instanceof ECPrivateKey ){
ASN1BitString wrappt = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(privateKeyInfo.getPrivateKey().getOctets()).getPublicKey();
publicKey = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(
new SubjectPublicKeyInfo(privateKeyInfo.getPrivateKeyAlgorithm(), wrappt).getEncoded() ));
}else if( privateKey instanceof EdDSAPrivateKey ){
publicKey = ((EdDSAPrivateKey)privateKey).getPublicKey();
}else ; // handle error if/as appropriate