javarsabouncycastle

Loading previously stored Keys fails in BouncyCastle with Java


I am generating an RSA Key in Java with Bouncy Castle. I am storing this key into a PKCS1 file and want to load this file on another part of the program back to a key file object.

My Key Storage is:

public static void writeKey(PemObject obj, String filename) throws IOException { 
    try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(filename)))
    {
        pemWriter.writeObject(obj);
        pemWriter.close();
    } catch (IOException ex) {
        Logger.getLogger(Conversion.class.getName()).log(Level.SEVERE, null, ex);
    }         
}

I convert the KeyPair Object by using:

public static PemObject createPrivateObject(KeyPair key) throws Exception {
    return new PemObject("RSA PRIVATE KEY", key.getPrivate().getEncoded());
}

This Exports into a File, which at first Looks good. OpenSSL is able to read this file and Show the Information about the values of the key.

However, later on I try to load the key from file with:

public static KeyPair readKeyPair(String path)
{
File privateKeyFile = new File(path);
    try (PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile))){
        
        
        Object object = pemParser.readObject();
        KeyPair kp;
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        kp = converter.getKeyPair((PEMKeyPair) object);
        pemParser.close();
        return kp;
        
    } catch (FileNotFoundException ex) {
        Logger.getLogger(Crypto.class.getName()).log(Level.SEVERE, null, ex);
    } catch (PEMException ex) {
        Logger.getLogger(Crypto.class.getName()).log(Level.SEVERE, null, ex);
    } catch (IOException ex) {
        Logger.getLogger(Crypto.class.getName()).log(Level.SEVERE, null, ex);
    } 
return null;

}

When I try this I get an error message

org.bouncycastle.openssl.PEMException: malformed sequence in RSA private key

( Full Message after text )

When I took a second look at OpenSSl, I saw OPENSSL printed at the end different values for the same key, the key within the file starts with

-----BEGIN RSA PRIVATE KEY-----
MIIEugIBADAN[...]

OpenSSL Outputs

openssl rsa -text -in Userkey.pem

Private-Key: (2048 bit)
modulus:
[...]
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIIEoAIBAAKC[...]

for the Keyfile generated and stored as described above. If I try and manually copy the Output of OpenSSL into the file and load this with my program, everything seems to work fine. So I assume something is wrong within the writeKey Subroutine but I can not figure out what is wrong in this. Does somebody know this?

Full Error Message:

   org.bouncycastle.openssl.PEMException: malformed sequence in RSA private key
at org.bouncycastle.openssl.PEMParser$KeyPairParser.parseObject(Unknown Source)
at org.bouncycastle.openssl.PEMParser.readObject(Unknown Source)
at certificatemanagement.Crypto.readKeyPair(Crypto.java:257)
at certificatemanagement.GuiDesignController.CreateCertificatePressed(GuiDesignController.java:250)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1769)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1657)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Node.fireEvent(Node.java:8411)
at javafx.scene.control.Button.fire(Button.java:185)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:380)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:294)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$354(GlassViewEventHandler.java:416)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:415)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.bouncycastle.openssl.PEMException: malformed sequence in RSA private key
at org.bouncycastle.openssl.PEMParser$RSAKeyPairParser.parse(Unknown Source)
... 70 more


 

Solution

  • Bouncy castle is writting PEM file in PKCS8 format, not PKCS1, and it never let you know that it didn't do what you were expecting.

    I don't know how to write PEM file in PKCS1 format. I would love to know, because I periodically spend an afternoon trying to do so before convincing myself that PCKS8 is just so better and superior, and no one need PKCS1 really, and just nobody talk me about PKCS1 again.

    So if you use the openssl command:

    openssl pkcs8 -topk8 -nocrypt -in private.pem

    in place of the one I suppose you tried (openssl rsa -in private.pem -check), you will get the same content as what bouncy castle wrote. Here again, we have a command which is just a little to smart about what it does, and don't tell you that "yeah, you tell me to read pcks1 RSA file, but look, headers are always lying, and I DO see that it's in fact pkcs8, so I will just read that as pkcs8 and everybody is happy right?"

    And so, your code should be adapted to read PCKS8 to something like (I didn't do Java since 10y, so perhaps there's some little things to adapt):

    public static PemObject createPrivateObject(KeyPair key) throws Exception {
      return new PemObject("PRIVATE KEY", key.getPrivate().getEncoded());
    }
    
    [...]
    
    public static KeyPair readKeyPair(String path) {
      File privateKeyFile = new File(path);
      try (PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile))){
    
        PrivateKeyInfo privkeyInfo = (PrivateKeyInfo)pemParser.readObject();
        PKCS8EncodedKeySpec keyspec = new PKCS8EncodedKeySpec(privkeyInfo.getEncoded);
        RSAPrivateKey privKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keyspec)
        pemParser.close();
        return kp;
    
    } catch { ....
    

    (EDIT: I'm glad I wrote that answer 5 years ago, because I have forgotten again)