Reputation: 3889
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.
Upvotes: 0
Views: 1772
Reputation: 38990
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
Upvotes: 1