RSAKey & openSSL-generated keys

This has been asked before over the years and there is no definitive answer

Is the consensus that JUCE’s RSAKey class is not compatible with public & private keys generated using openssl?

Over the past few days I have figured out how to parse PEM-formatted public keys. When I print the key as a hex string, it produces the same hex output as shown here for the same key:
https://8gwifi.org/PemParserFunctions.jsp
I know I am on the right track, but it seems like the juce::RSAKey is just not compatible with keys produced externally. using BigInteger::exponentModulo also produces garbage with the parsed values.

I am not sure, when loading this hex string into the BigInteger, whether or not the external SSL public key is stored in little endian or big-endian format.

Here is what I’m currently doing, which works up until the point of decryption:

    static juce::String convertPEMPublicKeyToString(juce::String pubKey)
    {
        jassert( pubKey.contains("-----BEGIN PUBLIC KEY-----"));
        jassert( pubKey.contains("-----END PUBLIC KEY-----"));
        jassert( pubKey.contains("MII")); //PEM keys start with 'MII'
        
        auto keyDataArr = juce::StringArray::fromLines(pubKey);
        keyDataArr.remove(keyDataArr.indexOf("-----END PUBLIC KEY-----"));
        keyDataArr.remove(0);
        keyDataArr.removeEmptyStrings();
        
        auto pemData = keyDataArr.joinIntoString("");
        
        DBG( "pemData: " );
        DBG( pemData );
        
        return pemData;
    }
    using PEMMemoryBlock = juce::MemoryBlock;
    using PEMDataType = juce::uint8;
    
    static PEMMemoryBlock convertPEMStringToPEMMemoryBlock(juce::String pemString)
    {
        PEMMemoryBlock mb;
        {
            juce::MemoryOutputStream mos(mb, false);
            auto ok = juce::Base64::convertFromBase64(mos, pemString);
            jassert(ok);
            juce::ignoreUnused(ok);
        }
        
        return mb;
    }

usage:

        auto pemString = PEMHelpers::convertPEMPublicKeyToString(pubKey);
        
        if( ! pemString.contains("MII") )
        {
            //it's not a PEM key.  abort!
            DBG( "invalid key!" );
            return;
        }
        
        auto pemData = PEMHelpers::convertPEMStringToPEMMemoryBlock(pemString);
//parsing of PEM data goes here...
//then...
        auto modulusHexStr = juce::String::toHexString(modulusBlock.getData(), modulus->length);
        modulusHexStr = modulusHexStr.removeCharacters(" ");
        DBG( "modulus hex: ");
        DBG( modulusHexStr );
        
        auto modulusBigInteger = juce::BigInteger();
        modulusBigInteger.parseString(modulusHexStr, 16);

        auto exponentHexStr = juce::String::toHexString(exponentBlock.getData(), exponent->length);
        exponentHexStr = exponentHexStr.removeCharacters(" ");
        DBG( "exponent hex: ");
        DBG( exponentHexStr );
        auto exponentBigInteger = juce::BigInteger();
        exponentBigInteger.parseString(exponentHexStr, 16);
        DBG( "exponent val: " );
        DBG( exponentBigInteger.toString(10));
        auto confirmation = resultVar["confirmation"].toString();
        auto confirmationBlock = PEMHelpers::convertPEMStringToPEMMemoryBlock(confirmation);
        auto confirmationHex = juce::String::toHexString(confirmationBlock.getData(),
                                                         confirmationBlock.getSize());
        
        juce::BigInteger confirmationBigInt;
        confirmationBigInt.parseString(confirmationHex, 16);
        confirmationBigInt.exponentModulo(exponentBigInteger, modulusBigInteger);
        auto decrypted = confirmationBigInt.toMemoryBlock();

decrypted never contains the correct result.
but the exponentHexStr and modulusHexStr printouts match the output from various online tools that show the exponent and modulus in hex for a given public key.
Also, the value printout matches the various online tools’ values for these massive exponents and modulos.

Comments?

1 Like

Alright, a little bit more sleuthing and I have figured it out!

I’m able to load an openssl-generated public key into a juce::RSAKey class, and then successfully decrypt a message from my server that was encrypted with an openssl-generated private key.

I’ll share the code in a bit after I test it out some more.

Fun fact, the decrypted string is reversed! That means there is finally a need for a juce::String::reverse member function!! :stuck_out_tongue_winking_eye:

1 Like

Congrats you made it!

Please note that encryption with private keys isn’t as secure as encryption with public keys. Usually you create signature with the private key, the receiver can verify with the public one. And encryption usually happens with the public one so that only the holder of the private key can decrypt a message.

Sure sure.

I’m just trying to get things working, and encrypting on the server with one key and decrypting with the other is all I’ve tried so far.
The php snippet I borrowed showed encrypting with the private key and decryption with the public key, so that’s why I encrypted with private, and sent the public key to my JUCE app.

A lot of what I used to get this working is ported from a pure javascript tool for parsing PEM files.
Since a lot of that project’s code is copied and edited from other sites, I am not sure how much of what I ported is actually needed or as well-written as it could be.
But i’ll share it so people can use/adapt/improve.

Ok, here is the php and C++.
I have tested it with keys that have 1024 bits, 2048, 4096, and 8192 bits.
It works as expected (decrypting successfully).

If testing if I can use the private key on the C++ side to decrypt messages encrypted on the server side with the public key fails, I will update accordingly.

Some of this code is ported from this javascript page: ASN.1 JavaScript decoder
Therefore, the ported code definitely has room for improvement.

Perhaps the JUCE team can add this functionality to the RSAKey class, now that I’ve demonstrated how to get the RSAKey class working with regular OpenSSL keys. Keys that were generated like this:

openssl genpkey -algorithm RSA -out ./key.pem  -pkeyopt rsa_keygen_bits:8192

openssl rsa -in ./key.pem -out ./publickey.pem -outform PEM -pubout

This very much depends on the principle that the private key is private. If you distribute software containing the private key, it’s possible to derive the public key and your “encryption” is now useless.

1 Like

@asimilon I’m well aware that private keys should remain private.

The code I shared is solely for the purpose of loading public openssl keys into the juce::RSAKey class, because no one has shared how to do that on the forum as of yet, and jules stated over a decade ago that supporting openssl keys in the juce RSAKey class is not on his todo list: (RSAKey and openssl compatibilities - #2 by jules).

Consider the code I shared as a starting place for supporting openssl keys in the juce::RSAKey class.
It is by no means a complete solution for us JUCE users.

3 Likes