使用 Bouncy Castle 从 PrivateKeyInfo 中提取公钥

Extract public key from PrivateKeyInfo with Bouncy Castle

提问人:Hakan54 提问时间:11/3/2023 最后编辑:Hakan54 更新时间:11/5/2023 访问量:127

问:

我正在尝试从私钥中获取公钥,对于某些用例,如果类型是实例和,则很简单。这两者可以很容易地转换为带有 .但是,如果我有一个类型的对象,并且我只能使用 提取私钥,并且我不确定是否有可用的实用程序或更好的方法来从中提取公钥。目前,我正在使用一种效率不高的解决方法,因为我创建了一个pem文件,然后使用pemparser重新读取它以提取公钥,这在方法中发生了。完整的代码片段见下文:PEMKeyPairPEMEncryptedKeyPairjava.security.KeyPairJcaPEMKeyConverterPrivateKeyInfoPKCS8EncryptedPrivateKeyInfoJcaPEMKeyConverterextractPublicKey

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>

任何知道是否有可能从中提取公钥,而无需使用方法中的解决方法,该方法临时创建私钥的 pem 字符串,并使用 PEMParser 重新处理以获取作为回报持有私钥和公钥的方法。PrivateKeyInfoextractPublicKeyJcaMiscPEMGeneratorPEMKeyPair

java ssl bouncycastle pem

评论

0赞 Topaco 11/4/2023
您的 PKCS#8 密钥是否仅包含 RSA 密钥?
0赞 Hakan54 11/4/2023
@Topaco它也可以是 EC
0赞 Topaco 11/4/2023
我只能使用此代码处理包含 RSA 密钥的 PKCS#8 密钥。EC (P-256) 和 Ed25519 导致异常,请参阅此处:onecompiler.com/java/3zsepdcx9。发布包含 EC 密钥且可处理的(非生产性)PKCS#8 密钥。
0赞 Hakan54 11/4/2023
@Topaco我忘了在代码片段中添加安全提供程序。我已经调整了它,也在我这边尝试了一下。EC 私钥现在通过,只有 PKCS#8 - ED25519 失败。您可以尝试使用新的代码片段吗?对于最后一个键,我只得到一个类广播异常。
0赞 Topaco 11/5/2023
如果实际上只使用一种密钥类型(例如 RSA),我建议使用特定于密钥的提取。当然,您也可以对多种密钥类型执行此操作(如答案中建议的那样)。但即便如此,也不是特别优雅。这是否真的是一种改进(在健壮性、安全性等方面)更多的是一个见仁见智的问题。

答:

1赞 dave_thompson_085 11/5/2023 #1

为什么它不适用于 Ed25519

[Jca]MiscPEMGenerator 为 RSA、DSA 和 EC(DSA) 生成 OpenSSL“传统”格式,该格式根据需要读回类型,但对于其他算法(包括 Ed25519),它会生成 PKCS8 格式 -- 与您开始使用的格式相同(除非即使您的输入未加密),它又读回 ,什么也没做。PEMParserPEMKeyPairPrivateKeyInfo

您能做些什么

对于 Ed25519,JCA API 实际上允许您直接生成公钥。其他算法则没有,但对于 RSA,在实践中始终使用的“CRT”子 API 包含所需的信息,而对于 EC,实践1 中的 PKCS8 格式包含所需的信息。只有对于DSA,你没有要求,但我包括了近乎完整的,你才需要一个不安全的黑客。(我没有包括 DH,因为实际上 DH 不与存储在任何地方的长期密钥一起使用。

1 PKCS8 中使用的 SEC1 ECPrivateKey 格式正式具有字段 OPTIONAL,但我从未计算过不生成它的软件。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

评论

0赞 Hakan54 11/6/2023
虽然看起来很冗长,但似乎这样就可以在不使用 PEMGenerator 和 PEMParser 的情况下提取私钥。谢谢伙计写下所有案例!