提问人:Hakan54 提问时间:11/3/2023 最后编辑:Hakan54 更新时间:11/5/2023 访问量:127
使用 Bouncy Castle 从 PrivateKeyInfo 中提取公钥
Extract public key from PrivateKeyInfo with Bouncy Castle
问:
我正在尝试从私钥中获取公钥,对于某些用例,如果类型是实例和,则很简单。这两者可以很容易地转换为带有 .但是,如果我有一个类型的对象,并且我只能使用 提取私钥,并且我不确定是否有可用的实用程序或更好的方法来从中提取公钥。目前,我正在使用一种效率不高的解决方法,因为我创建了一个pem文件,然后使用pemparser重新读取它以提取公钥,这在方法中发生了。完整的代码片段见下文:PEMKeyPair
PEMEncryptedKeyPair
java.security.KeyPair
JcaPEMKeyConverter
PrivateKeyInfo
PKCS8EncryptedPrivateKeyInfo
JcaPEMKeyConverter
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>
任何知道是否有可能从中提取公钥,而无需使用方法中的解决方法,该方法临时创建私钥的 pem 字符串,并使用 PEMParser 重新处理以获取作为回报持有私钥和公钥的方法。PrivateKeyInfo
extractPublicKey
JcaMiscPEMGenerator
PEMKeyPair
答:
为什么它不适用于 Ed25519
[Jca]MiscPEMGenerator
为 RSA、DSA 和 EC(DSA) 生成 OpenSSL“传统”格式,该格式根据需要读回类型,但对于其他算法(包括 Ed25519),它会生成 PKCS8 格式 -- 与您开始使用的格式相同(除非即使您的输入未加密),它又读回 ,什么也没做。PEMParser
PEMKeyPair
PrivateKeyInfo
您能做些什么
对于 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
评论