I am trying to obtain the public key from a private key, for some use cases it is simple if the type is of the instance PEMKeyPair
and PEMEncryptedKeyPair
. These two can be easily converted to a java.security.KeyPair
with JcaPEMKeyConverter
. However if I have an object of the type PrivateKeyInfo
and PKCS8EncryptedPrivateKeyInfo
I can only extract the private key with the JcaPEMKeyConverter
and I am not sure if there is an utility available or a better way to also extract the public key from it. Currently I am using a workaround which is not that efficient as I create a pem file and then reread it with a pemparser to extract the public key, this happends in the extractPublicKey
method. See below for the full code snippet:
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-----
""";
}
I used Java 17 and the following bouncy castle dependency:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.76</version>
</dependency>
Any idea whether it is possible to extract the public key from a PrivateKeyInfo
without the need of using the workaround in the method extractPublicKey
which temporally creates a pem string of the private key with JcaMiscPEMGenerator
to reprocess with the PEMParser to obtain the PEMKeyPair
which in return holds the private and public key.
Why it doesn't work for Ed25519
[Jca]MiscPEMGenerator
generates the OpenSSL 'traditional' formats for RSA, DSA, and EC(DSA), which PEMParser
reads back to PEMKeyPair
type as desired, but for other algorithms including Ed25519 it generates PKCS8 format -- the same format you started with (except not encrypted even if your input was), which reads back to PrivateKeyInfo
, accomplishing nothing.
What you can do
For Ed25519 the JCA API actually allows you to generate the public key directly. The other algorithms don't, but for RSA the 'CRT' sub-API which in practice is always used contains the needed information and for EC the PKCS8 format in practice1 does. Only for DSA, which you didn't ask for but I included for near-completeness, do you need an unsafe hack. (I didn't include DH because in practice DH is not used with long-term keys that would be stored anywhere.)
1 The SEC1 ECPrivateKey format used in PKCS8 formally has the publicKey
field OPTIONAL, but I have never encounted software that doesn't generate it.
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