import java.io.ByteArrayInputStream; import java.io.StringReader; import java.io.StringWriter; import java.security.*; import java.security.cert.X509Certificate; import java.security.interfaces.ECPrivateKey; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.openssl.*; import org.bouncycastle.openssl.jcajce.*; import org.bouncycastle.operator.InputDecryptorProvider; import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; import org.springframework.security.converter.RsaKeyConverters; /** The same jobs done with BouncyCastle (bcpkix), with Spring Security's RsaKeyConverters, and with the JDK. */ public class Migration { static final char[] PW = "changeit".toCharArray(); static Object bcParse(String pem) throws Exception { try (PEMParser p = new PEMParser(new StringReader(pem))) { return p.readObject(); } } public static void main(String[] args) throws Exception { PEMDecoder jdk = PEMDecoder.of(); JcaPEMKeyConverter conv = new JcaPEMKeyConverter(); Fx.h("1. load an RSA private key (PKCS#8)"); PrivateKey viaBc = conv.getPrivateKey((PrivateKeyInfo) bcParse(Fx.read("rsa-private-pkcs8.pem"))); PrivateKey viaJdk = jdk.decode(Fx.read("rsa-private-pkcs8.pem"), PrivateKey.class); Fx.check(viaBc.equals(viaJdk), "BouncyCastle and the JDK produce equal keys"); System.out.println("BC parser returned: " + bcParse(Fx.read("rsa-private-pkcs8.pem")).getClass().getSimpleName()); Fx.h("2. load a certificate"); X509CertificateHolder holder = (X509CertificateHolder) bcParse(Fx.read("server.pem")); X509Certificate c1 = new JcaX509CertificateConverter().getCertificate(holder); X509Certificate c2 = jdk.decode(Fx.read("server.pem"), X509Certificate.class); Fx.check(c1.equals(c2), "same certificate either way"); Fx.h("3. write keys"); StringWriter sw = new StringWriter(); try (JcaPEMWriter w = new JcaPEMWriter(sw)) { w.writeObject(c2.getPublicKey()); } Fx.check(sw.toString().replace("\r\n", "\n").equals(PEMEncoder.of().encodeToString(c2.getPublicKey()).replace("\r\n", "\n")), "JcaPEMWriter and PEMEncoder write identical public-key text (line terminators aside)"); sw = new StringWriter(); try (JcaPEMWriter w = new JcaPEMWriter(sw)) { w.writeObject(viaJdk); } System.out.println("JcaPEMWriter on an RSA private key writes: " + sw.toString().lines().findFirst().get()); System.out.println("PEMEncoder on an RSA private key writes: " + PEMEncoder.of().encodeToString(viaJdk).lines().findFirst().get()); Fx.h("4. encrypted PKCS#8 key (BC needs its own provider for AES/CBC/PKCS7Padding; the JDK does not)"); PKCS8EncryptedPrivateKeyInfo bcEnc = (PKCS8EncryptedPrivateKeyInfo) bcParse(Fx.read("rsa-private-encrypted.pem")); InputDecryptorProvider dec = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()).build(PW); PrivateKey k1 = conv.getPrivateKey(bcEnc.decryptPrivateKeyInfo(dec)); PrivateKey k2 = jdk.withDecryption(PW).decode(Fx.read("rsa-private-encrypted.pem"), PrivateKey.class); Fx.check(k1.equals(k2), "same key either way"); Fx.h("5. what BouncyCastle reads and the JDK does not"); Object sec1 = bcParse(Fx.read("ec-private-sec1.pem")); KeyPair bcPair = conv.getKeyPair((PEMKeyPair) sec1); System.out.println("EC PRIVATE KEY (SEC1): BC -> " + sec1.getClass().getSimpleName() + ", JDK -> " + Fx.describe(jdk.decode(Fx.read("ec-private-sec1.pem")))); PrivateKey ecViaJdk = jdk.decode(Fx.read("ec-private-pkcs8.pem"), PrivateKey.class); Fx.check(((ECPrivateKey) bcPair.getPrivate()).getS().equals(((ECPrivateKey) ecViaJdk).getS()), "after 'openssl pkcs8 -topk8 -nocrypt' the JDK reads the same secret scalar BC read from SEC1"); Object legacy = bcParse(Fx.read("rsa-private-legacy-encrypted.pem")); PEMDecryptorProvider legacyDec = new JcePEMDecryptorProviderBuilder().setProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()).build(PW); KeyPair legacyKp = conv.getKeyPair(((PEMEncryptedKeyPair) legacy).decryptKeyPair(legacyDec)); System.out.println("Proc-Type: 4,ENCRYPTED: BC -> " + legacy.getClass().getSimpleName() + " decrypted to " + legacyKp.getPrivate().getAlgorithm()); Fx.check(legacyKp.getPrivate().equals(viaJdk), "...and it is the same RSA key"); Fx.tryIt("Proc-Type: 4,ENCRYPTED: JDK", () -> jdk.withDecryption(PW).decode(Fx.read("rsa-private-legacy-encrypted.pem"), PrivateKey.class)); Fx.h("6. Spring Security RsaKeyConverters (RSA only; PKCS#8 and X.509 only)"); Fx.check(RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(Fx.read("rsa-private-pkcs8.pem").getBytes())).equals(viaJdk), "pkcs8() reads the RSA private key"); Fx.check(RsaKeyConverters.x509().convert(new ByteArrayInputStream(Fx.read("rsa-public.pem").getBytes())).equals(jdk.decode(Fx.read("rsa-public.pem"), PublicKey.class)), "x509() reads the RSA public key"); Fx.tryIt("pkcs8() on an EC key", () -> RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(Fx.read("ec-private-pkcs8.pem").getBytes()))); Fx.tryIt("pkcs8() on the PKCS#1 key", () -> RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(Fx.read("rsa-private-pkcs1.pem").getBytes()))); Fx.tryIt("JDK on the EC key", () -> jdk.decode(Fx.read("ec-private-pkcs8.pem"), PrivateKey.class)); } }