Reading, writing and converting RSA keys in PEM, DER, PUBLICKEYBLOB and PRIVATEKEYBLOB formats

This post finishes my epic about the implementation of RSA encryption. See the part I and part II of my post about RSA encryption for C++/Delphi (CryptoAPI) and PHP (OpenSSL) applications.

The main problem we faced was incompatibility of key formats. CryptoAPI uses PRIVATEKEYBLOB and PUBLICKEYBLOB formats to export and import RSA keys while OpenSSL extension for PHP uses PEM format. In order to use both libraries in communicating applications we needed some tool to convert keys from one format to another. The only tool we found for this was OpenSSL 1.0.x beta. Notice that earlier versions of OpenSSL do not support CryptoAPI BLOBs.

Update: It was found later that CryptoAPI has native functions for key conversion. See “Update” section at the bottom of the post.

Below is a command line syntax example for conversion of private key from PEM to PRIVATEKEYBLOB format:

openssl rsa -inform PEM -in private.pem -outform MS\ PRIVATEKEYBLOB -out private.blob

And this example converts PUBLICKEYBLOB to PEM format:

openssl rsa -pubin -inform MS\ PUBLICKEYBLOB -in public.blob -outform PEM -out public.pem

Notice that backslash (\) in format names. You need to type it as it actually escapes the space character.

However, we found some drawbacks in usage of OpenSSL 1.0.x beta:

  • There was no Windows build of it available at the time of the post but we wanted to convert keys on Windows.
  • We also wanted to convert keys directly in our code w/o any need for external application.

As far as PRIVATEKEYBLOBPUBLICKEYBLOB and PEM format structures are known, we decided to develop code that will read and write them using low-level functions. It actually took 1-2 days for me to develop that code so I don’t think it’s a really hard task.

Later we faced another problem: PHP versions prior to 5.2 don’t support openssl_pkey_get_details function. Once again, handling key formats directly helped us to resolve the issue by providing a replacement for the function.

So, let me explain how you can implement reading/writing PEM, DER, PRIVATEKEYBLOB and PUBLICKEYBLOB formats with some code examples in PHP for PEM and DER formats and in C++/VCL for CryptoAPI BLOBs. As the task was a part of a commercial project I cannot post a complete working example here. But I will do my best helping you to assemble such code on your own. You can also request our service at Pumka.net.

Understanding RSA Key Formats

The first thing you need to know is that any key format is actually a container for the set of long numbers. All other data could be considered “noise”.

  • Private key contains: modulus, private exponent, public exponent, prime 1, prime 2, exponent 1, exponent 2 and coefficient.
  • Public key contains only modulus and public exponent.

Once you can read these numbers from one format and put them to another, you can covert keys. You can also export public keys for private ones this way.

PEM format produced by OpenSSL is actually base64 encoded and wrapped key data in the binary format called DER. Thus, to work with PEM format you must actually work with DER.

DER format is based on Abstract Syntax Notation One (ASN.1) standard. The standard specifies encoding of tree-like data structures. Two predefined data structures are used for private and public RSA keys. Though I didn’t find a good parser for the format, with a couple of notes from the standard in hand I wrote a class for encoding and decoding ASN.1 values.

PRIVATEKEYBLOB and PUBLICKEYBLOB formats are actually C-style PRIVATEKEYBLOB and PUBLICKEYBLOB structures. You can use pointer manipulation and memory copy to parse them. Notice that these formats use little-endian byte order for integer values unlike DER format which uses big-endian order. Thus, you’ll need to reverse integer byte sequences when converting key data from BLOB to DER or vise versa.

Getting DER Data out of PEM

As I wrote, PEM format contains base64 encoded DER data. It also adds a header and footer to it. Below is an example of PEM file for a private key.

-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAKIQa3Oic8/OOxMpVNUPsKrsSCvbOlKVQXKVRPX85VNkcYqx+Cr6
Kij/bfnLFQ6E2Of7hoZZsYjFLNsKcH14R4MCAwEAAQJBAJIefdMfiI23YroPDOah
I+en7BZmrfohioXWIfwsDVQWj22gDtB7whni1TqVQYDC3tqq8OtreIBuH1IS2784
dmkCIQDSeOC/a5uZaJQgA54N+wGqOr3q8ZqgMHS1wFc527QFrQIhAMUe5RNLps1Y
x1ReFU2DWT7zjqKM5gr0ffUAAusQpEfvAiAF1DEtO/awNfQ8Or1q17PBGiVeV1iX
7R+eVPhVct82dQIgcIbmdlFlcywO/haHSqyEse8PqbONTwurK8VJ5S6m2XkCIQCg
TC59mfa6vHyTPBdUliMnD2l9mUJvVkcpwr7yyxHT6A==
-----END RSA PRIVATE KEY-----

Thus, you can get DER data by removing header, footer and base64 decoding:

function PemToDer($Pem) {
    //Split lines:
    $lines = explode("\n", trim($Pem));
    //Remove last and first line:
    unset($lines[count($lines)-1]);
    unset($lines[0]);
    //Join remaining lines:
    $result = implode('', $lines);
    //Decode:
    $result = base64_decode($result);

    return $result;
}

Great! Now you can touch naked DER data. 😉

Below is the function that does the opposite: encodes pure DER data into PEM format. Notice that lines with base64 encoded data must be 65 character wide.

function DerToPem($Der, $Private=false)
{
    //Encode:
    $Der = base64_encode($Der);
    //Split lines:
    $lines = str_split($Der, 65);
    $body = implode("\n", $lines);
    //Get title:
    $title = $Private? 'RSA PRIVATE KEY' : 'PUBLIC KEY';
    //Add wrapping:
    $result = "-----BEGIN {$title}-----\n";
    $result .= $body . "\n";
    $result .= "-----END {$title}-----\n";

    return $result;
}

Parsing DER Format

You can view the structure and contents of DER files with ASN.1 Editor. I highly recommend this tool for understanding DER format and debugging your code.

Let’s open some private key file with ASN.1 Editor:

private

I added green markers pointing to particular values.

Below is the corresponding public key:

public

Despite of less real data, the structure of public key file looks more complicated. But the header sequence is always the same for any RSA public key so you can simply replicate it when writing the file.

You can download my PHP class for decoding and encoding ASN.1 values here: ASNValue.class.php.
Use Decode method to parse DER record into Tag and Value properties. Alternatively, set Tag and Value properties (you can do this with the constructor) and call Encode method to compose DER record.

Notice that many integer values in DER begin with a zero byte. That’s because ASN.1 standard requires adding zero byte to the beginning of multi-byte integer if the first bit of it is set to 1. I added methods for proper encoding and decoding of integers as well as for parsing and composing sequences.

Wow! Now we can read private key values from DER data:

//Decode root sequence
$body = new ASNValue;
$body->Decode($PrivateDER);
$bodyItems = $body->GetSequence();

//Read key values:
$Modulus = $bodyItems[1]->GetIntBuffer();
$PublicExponent = $bodyItems[2]->GetInt();
$PrivateExponent = $bodyItems[3]->GetIntBuffer();
$Prime1 = $bodyItems[4]->GetIntBuffer();
$Prime2 = $bodyItems[5]->GetIntBuffer();
$Exponent1 = $bodyItems[6]->GetIntBuffer();
$Exponent2 = $bodyItems[7]->GetIntBuffer();
$Coefficient = $bodyItems[8]->GetIntBuffer();

And you can export public key DER data:

//Encode key sequence
$modulus = new ASNValue(ASNValue::TAG_INTEGER);
$modulus->SetIntBuffer($Modulus);
$publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
$publicExponent->SetInt($PublicExponent);
$keySequenceItems = array($modulus, $publicExponent);
$keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
$keySequence->SetSequence($keySequenceItems);
//Encode bit string
$bitStringValue = $keySequence->Encode();
$bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte
$bitString = new ASNValue(ASNValue::TAG_BITSTRING);
$bitString->Value = $bitStringValue;
//Encode body
$bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode();
$body = new ASNValue(ASNValue::TAG_SEQUENCE);
$body->Value = $bodyValue;
//Get DER encoded public key:
$PublicDER = $body->Encode();

Well, you can also use my class to read public key data and write private keys. I leave this for your homework.

Parsing CryptoAPI BLOBs

Since PRIVATEKEYBLOB and PUBLICKEYBLOB formats correspond to C structures, they could be parsed with pointer manipulation and memory copy tricks. Our client application built with C++ Builder and we used VCL TMemoryStream class for memory manipulation.

Below code example demonstrates parsing PRIVATEKEYBLOB format:

//Set structure pointers:
BLOBHEADER* blobheader = (BLOBHEADER*)PrivateBLOB->Memory;
RSAPUBKEY* rsapubkey = (RSAPUBKEY*)((System::PByte) PrivateBLOB->Memory + sizeof(BLOBHEADER));
//Get key length:
unsigned int byteLength = rsapubkey->bitlen/8;
unsigned int halfLength = byteLen/2;
//Set value pointers:
System::PByte modulus = ((System::PByte)PrivateBLOB->Memory + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY));
System::PByte prime1 = modulus + byteLen;
System::PByte prime2 = prime1 + halfLen;
System::PByte exponent1 = prime2 + halfLen;
System::PByte exponent2 = exponent1 + halfLen;
System::PByte coefficient = exponent2 + halfLen;
System::PByte privateExponent = coefficient + halfLen;
//Create streams
TMemoryStream* Modulus= new TMemoryStream;
TMemoryStream* Prime1 = new TMemoryStream;
TMemoryStream* Prime2 = new TMemoryStream;
TMemoryStream* Exponent1 = new TMemoryStream;
TMemoryStream* Exponent2 = new TMemoryStream;
TMemoryStream* Coefficient = new TMemoryStream;
TMemoryStream* PrivateExponent = new TMemoryStream;
//Read values:
unsigned long PublicExponent = rsapubkey->pubexp;
Modulus->WriteBuffer(modulus, byteLength);
Prime1->WriteBuffer(prime1, halfLength);
Prime2->WriteBuffer(prime2, halfLength);
Exponent1->WriteBuffer(exponent1, halfLength);
Exponent2->WriteBuffer(exponent2, halfLength);
Coefficient->WriteBuffer(coefficient, halfLength);
PrivateExponent->WriteBuffer(privateExponent, byteLength);

In a similar way you can export PUBLICKEYBLOB with known modulus and public exponent values:

//Prepare structures
BLOBHEADER blobheader;
	blobheader.bType = PUBLICKEYBLOB;
	blobheader.bVersion = 2;
	blobheader.reserved = 0;
	blobheader.aiKeyAlg = CALG_RSA_KEYX;
RSAPUBKEY rsapubkey;
	rsapubkey.magic = PUBLICKEYBLOB_MAGIC; //RSA1
	rsapubkey.bitlen = Modulus->Size*8;
	rsapubkey.pubexp = PublicExponent;
//Write structures
PublicBLOB->WriteBuffer((void*)&blobheader, sizeof(BLOBHEADER));
PublicBLOB->WriteBuffer((void*)&rsapubkey, sizeof(RSAPUBKEY));
//Write modulus
PublicBLOB->CopyFrom(Modulus, 0);

Once again, I left decoding PUBLICKEYBLOB and encoding PRIVATEKEYBLOB for your homework.

Conclusion

Low-level access to RSA key formats provides easy solution for key format incompatibility problem that could look as an irresistible hinder between OpenSSL and CryptoAPI.

We implemented this technique for on-fly conversion of key BLOBs exported by CryptoAPI into native OpenSSL format (PEM) and vise versa. This allowed as to establish compatible and reliable encrypted communication between our applications using different cryptographic libraries.

We also applied parsing of PEM format for replacement of openssl_pkey_get_details function, which is not available prior to PHP v5.2.

Download Source Code:

ASNValue.class.php PHP class for encoding and decoding ASN.1 values.

Update: Converting RSA keys with CryptoAPI

Thanks to Jason who recently contacted me about this post it was found that CryptoAPI has native functions for PEM/DER to BLOB conversion.
Just a quick sample I didn’t test myself yet but it works fine for him:

// Convert from PEM format to DER format
// removes header and footer and decodes from base64
if (!CryptStringToBinaryA((char*)pbPublicPEM, iPEMSize, CRYPT_STRING_ANY,
    pbPublicDER, &iDERSize, NULL, NULL)
) {
    // Error handling
}

// Decode from DER format to CERT_PUBLIC_KEY_INFO. This has the public key
// in ASN.1 encoded format called "SubjectPublicKeyInfo" ... szOID_RSA_RSA
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, pbPublicDER,
    iDERSize, CRYPT_ENCODE_ALLOC_FLAG, NULL, &pbPublicKey, &iPBLOBSize)
) {
    // Error handling
}

// Decode the RSA Public key itself to a PUBLICKEYBLOB
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
    pbPublicKey->PublicKey.pbData, pbPublicKey->PublicKey.cbData,
    CRYPT_ENCODE_ALLOC_FLAG, NULL, &pbPKEY, &iPKEYSize)
) {
    // Error handling
}

45 thoughts on “Reading, writing and converting RSA keys in PEM, DER, PUBLICKEYBLOB and PRIVATEKEYBLOB formats”

  1. Example “openssl rsa -inform MS\ PUBLICKEYBLOB -in public.blob -outform PEM -out public.pem”
    for converting PUBLICKEYBLOB to PEM format isn’t working. It expects private key for input. But, with “-pubin” parameter its’s working:
    openssl rsa -pubin -inform MS\ PUBLICKEYBLOB -in public.blob -outform PEM -out public.pem”

  2. Very great job !

    But could you explain me how to convert your “GetIntBuffer()” format into a hexadecimal as we can see in ASN.1 Editor ?

    Thank you in advance.

  3. I do your homework try to convert rsa key in xml to pem format. I could write public key but not private key. Could you advise? Many thanks.

    Here are the code:
    ——————————————
    RSA private key in XML format:

    4OzMyHiNCA7RJ1XYNfeT6C9TvGNCWqQ5Gy/dIO2raVcKO/mVK8o8JF+m16JIGoFi36qlhI9fyvznKHwg+
    YO5+VIvmXeLwgmSD+53z76aYKJZNDjJfLPvcNrVCjdKyBRxqx2OsqGX2IyDOCQPCw7vB0zB4mKkvS8s38ezpCxUDWs=
    AQAB
    9vPCANVW1pFNfFsCvoGrk9bDVKTekXlmPVRK+PSNDO+gXTEEnsGg8/JW8FiaAu1L7M/qjv/BzzpX56Cwr652/Q==
    6SpxOEbJqbuGudQeJTdb9/NY8T5jzSSTAnavPasXjvHlFhqGlrWf/ruSPIIyIHpMrsw05/0oWqcMIs8g09zmhw==
    P5J6o8pHw46+OsAXEcHB0JodyKF0plBxMGKNtk8GLIlXaAYhYVkJPSNqnA7C/vPlcr4HbO6MDvO/A2LLeT5ZZQ==
    sw481BLIWuf/lkUmwT26Crd93gUjby5fFs091gXFmah5XHgSUzo3G8+/hWgWRoqkyLdZCa6HhtYxo6mcdBZ3kQ==
    Nfm35c6uYZjODd2ld/StiVY56UGIDVSUK34fe2myvKUKyWNNCTTui6hmJAbWWMaebof5JXtnUdtu/7EpFs6erA==
    LvbCu4kQSYJugXwXLQ2IwI5BObCwBiUMF3UjAuEdTV1zMZqnXwFXFXEzcQrda0tgzbrf3JHjOosb6QQiDndGuTgEEgk
    vXdc/sXyFEJ17OBLRxfpdwAhYflTNARJiqYFe975ZTz3wgDcKpvZO3mRnXuF2NQDnhdQxMqEOLEo5vSE=

    ——————————————
    and here goes the function to convert XML to PEM format:

    function XmlToPem ($Modulus, $Exponent, $P = ”, $Q = ”, $DP = ”, $DQ = ”, $InverseQ = ”, $D = ”) {
    //Encode key sequence
    $Modulus = base64_decode($Modulus);
    $PublicExponent = hexdec(bin2hex(base64_decode($Exponent)));

    if ( (strlen($P) > 0) && (strlen($Q) > 0) && (strlen($DP) > 0) && (strlen($DQ) > 0) && (strlen($InverseQ) > 0) && (strlen($D) > 0) ) {
    $private = true;

    $PrivateExponent=base64_decode($D);
    $Prime1 = base64_decode($P);
    $Prime2 =base64_decode($Q);
    $Exponent1 =base64_decode($DP);
    $Exponent2 = base64_decode($DQ);
    $Coefficient = base64_decode($InverseQ);
    } else {
    $private = false;
    }

    //Encode key sequence
    if ( $private) {
    $modulus = new ASNValue(ASNValue::TAG_INTEGER);
    $modulus->SetIntBuffer($Modulus);
    $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
    $publicExponent->SetInt($PublicExponent);
    $privateExponent = new ASNValue(ASNValue::TAG_INTEGER);
    $privateExponent->SetIntBuffer($PrivateExponent);
    $prime1 = new ASNValue(ASNValue::TAG_INTEGER);
    $prime1->SetIntBuffer($Prime1);
    $prime2 = new ASNValue(ASNValue::TAG_INTEGER);
    $prime2->SetIntBuffer($Prime2);
    $exponent1 = new ASNValue(ASNValue::TAG_INTEGER);
    $exponent1->SetIntBuffer($Exponent1);
    $exponent2 = new ASNValue(ASNValue::TAG_INTEGER);
    $exponent2->SetIntBuffer($Exponent2);
    $coefficient = new ASNValue(ASNValue::TAG_INTEGER);
    $coefficient->SetIntBuffer($Coefficient);

    $keySequenceItems = array($modulus, $publicExponent, $privateExponent, $prime1, $prime2, $exponent1, $exponent2, $coefficient);
    } else {
    $modulus = new ASNValue(ASNValue::TAG_INTEGER);
    $modulus->SetIntBuffer($Modulus);
    $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
    $publicExponent->SetInt($PublicExponent);

    $keySequenceItems = array($modulus, $publicExponent);
    }

    $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
    $keySequence->SetSequence($keySequenceItems);

    //Encode bit string
    $bitStringValue = $keySequence->Encode();
    $bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte

    $bitString = new ASNValue(ASNValue::TAG_BITSTRING);
    $bitString->Value = $bitStringValue;

    //Encode body
    $bodyValue = /*”\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00″*/” . $bitString->Encode();
    $body = new ASNValue(ASNValue::TAG_SEQUENCE);
    $body->Value = $bodyValue;

    //Get DER encoded private/public key:
    return DerToPem($body->Encode(), $private);

    }
    ——————————————
    Then I copy and paste values from XML format and call the conversion function as follows:

    $result = XmlToPem( ‘4OzMyHiNCA7RJ1XYNfeT6C9TvGNCWqQ5Gy/dIO2raVcKO/mVK8o8JF+m16JIGoFi36qlhI9fyvznKHwg+YO5+
    VIvmXeLwgmSD+53z76aYKJZNDjJfLPvcNrVCjdKyBRxqx2OsqGX2IyDOCQPCw7vB0zB4mKkvS8s38ezpCxUDWs=’,
    ‘AQAB’, ‘9vPCANVW1pFNfFsCvoGrk9bDVKTekXlmPVRK+PSNDO+gXTEEnsGg8/JW8FiaAu1L7M/qjv/BzzpX56Cwr652/Q==’, ‘6SpxOEbJqbuGudQeJTdb9/NY8T5jzSSTAnavPasXjvHlFhqGlrWf/ruSPIIyIHpMrsw05/0oWqcMIs8g09zmhw==’, ‘P5J6o8pHw46+OsAXEcHB0JodyKF0plBxMGKNtk8GLIlXaAYhYVkJPSNqnA7C/vPlcr4HbO6MDvO/A2LLeT5ZZQ==’, ‘sw481BLIWuf/lkUmwT26Crd93gUjby5fFs091gXFmah5XHgSUzo3G8+/hWgWRoqkyLdZCa6HhtYxo6mcdBZ3kQ==’, ‘Nfm35c6uYZjODd2ld/StiVY56UGIDVSUK34fe2myvKUKyWNNCTTui6hmJAbWWMaebof5JXtnUdtu/7EpFs6erA==’, ‘LvbCu4kQSYJugXwXLQ2IwI5BObCwBiUMF3UjAuEdTV1zMZqnXwFXFXEzcQrda0tgzbrf3JHjOosb6QQiDndGuTgEEgk
    vXdc/sXyFEJ17OBLRxfpdwAhYflTNARJiqYFe975ZTz3wgDcKpvZO3mRnXuF2NQDnhdQxMqEOLEo5vSE=’);

    echo $result;

    The result is NOT same as the original.

    • I’m not sure what word “XML” is doing here but your mistake is that you trying to put private key values to the same DER structure as a public key. Please read “Parsing DER Format” in this post carefully and compare pictures for private and public key structures.

      The private key structure is quite straightforward and the code to write it must be very easy. It’s just a sequence of integers and there is no need for bit string, header, etc.
      I recommend to use separate functions to write public and private keys as they have almost no common.

      To debug you code, you can output DER file before conversion to PEM and view it with ASN.1 Editor to easily find what’s wrong.

      Hope that helps.

  4. Hi and thanks for very illuminating walkthrough.

    I managed to extract public key and exponent and encrypt in .NET classes. First I forgot to remove leading null in pubkey but then it decrypts in OpenSSL as it should.

    But Win32 CryptEncrypt makes me crazy and OpenSSL always screems about padding parsing fails or similar.

    I reversed output from CryptEncrypt.

    But also read about limitations on whether which provider to use, that Microsoft Base only takes 512 bits max or something.

    Is there something about this that still failes?

    Or some setting for padding that must be other than default.

    My keys use 2048 bits.

    Anything anybody stumbled over?

    Thanks.
    Best regards
    Lars

    • Can you explain more your encryption/description scheme? I.e. which components you use to encrypt and which to decrypt the messages?
      My recommendation is to try encrypt and decrypt the message with the same component then try to encrypt/decrypt the same message with another and see if there is any binary difference in the encrypted message.

  5. Hi Anton,
    first off all thanks for your effort to explain this to us.

    I have a little BIG problem…
    i program with .net compact framework and have a lot off problem trying to encrypt using RSA algorithm.
    with your explain i really think that i’m close but… always have a BUT!
    I use openssl to gerate my private key (PEM Format).
    with your article i “transform” my private Key PEM to BLOB.
    I import this to my .net cf application, like that:
    string path = GetDirectoryName() + “\\PrivateKey.Blob”;
    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.ImportCspBlob(StreamFile(path));
    Convert.ToBase64String(rsa.Encrypt(“HELLO”, false));
    my problem is the result is different, if i try to encrypt the same string using openssl

    Any idea what happen?!
    Thks

    • To clarify: You’re able to import the key to .NET crypto provider and it encrypts and decrypts data successfully with it?
      If so, the problem is definitely not with the key and import. It’s likely different byte order but could be also different padding settings while unlikely.
      Another cause may be UTF-16 text encoding in .NET.

      What kind of errors do you get trying to decrypt the messages with OpenSSL and .NET? You can email me binary files with exact encrypted messages and the key so I can look what’s different in them?

      • Hi Anton,
        First off all, thanks for your rapid answer.
        After convert my private key PEM, generate with OpenSSL, to BLOB i import then to .net cf with “rsa.ImportCspBlob(StreamFile(path));”.
        With that i can encrypt the msg and decrypt using my private key, throw my .net cf application, and the decrypted msg is the same encrypt msg.
        But there’s some problems, one off then is every time i encrypt the same msg he return different encrypted value!!!
        Other problem is that if i try to verify, using openssl, my encrypted msg with puclic key the verification fails!!!

        • Just one more thing…
          If i encrypt msg with privateKey and using openSSL, he return always the same msg!

          • Yes, the encrypted message for the same key and original text must always be the same. I really have no idea why .NET crypto provider makes it different each time. Maybe it adds some kind of salt or somewhat. I suggest to ask on .NET boards about that.

  6. Hey i need you help

    i read your code for transform DER 2 PEM files, but i don know what is a $PrivateDER, is a string or a file id? please help me!

    Thanks!!

    • Hi! It’s a binary string holding DER key. You can get it from DER file by file_get_contents($fileName, FILE_BINARY) or you can get it from PEM file contents by my PemToDer function listed above.

  7. Isn’t missing $noise = $bodyItems[1]->GetInt(); and shift the index in the remaining items in $bodyItems to +1 to read the “(4,1) INTERGER : ‘0’ <- noise??" part properly?

    • Freddy, GetSequence method reads all parts of the sequence into zero-based array. If you need to read the first value (“noise”) then you have to read zero element of the array:
      $noise = $bodyItems[0]->GetInt();

      I simply skip it because it’s of no use.

  8. Can an equivalent of Java code be given .Have tried removing the header and footer and calling the base64decoder .The byte[] does not load the publickey.It gives invalid keyformat
    Any help ?

    • I’m sorry, I’m not very familiar with Java.
      Notice that you base64-encoded contents is split into multiple lines. You have to concatenate them in order to properly decode.
      Home this helps.

      If no, please email me your source code. I’ll look what’s wrong with it.

    • Hi!
      Please read the post carefully. It contains all the guidelines for parsing PEM format.
      In short, PEM is nothing more that base64 encoded version of DER format. So you have to base64 decode PEM body first to get DER, then you can parse DER with my AsnValue class.

    • Hi!
      Surprisingly, another guy recently contacted me and suggested using native CryptoAPI functions for key conversion. According to his findings CryptStringToBinaryA function can decode PEM to DER.
      Please find “Update…” section I added to the bottom of the post.

  9. I land here because I want to convert a Private key to XML.

    OK. I was also shocked, “PKCS8 to XML, please give me a break” I think.

    Well, it happens that some web service I am trying to consume ask me for that sort of thing, and I discover there’s actually some .NET library that requires such structure :

    [RSAKeyValue]
    [Modulus]1KHjZtqkEv1ECT…X8bnZk=[/Modulus]
    [Exponent]AQAB[/Exponent]
    [P]9g3MtFSVVE…bm68+HIobw==[/P]
    [Q]3To87MOrKL7RvKP…RFdOdw==[/Q]
    [DP]jXOpdxIZNvrcJ7RiKff..+0c6Q==[/DP]
    [DQ]VlB8qNwHEdSxvGoi2np..3mLQ==[/DQ]
    [InverseQ]EtwABGpy/cumIxHI5..RMvWmQ==[/InverseQ]
    [D]PEg5RCuwYIHVDBgVcWeXD9q6TsUQ..m0mIrRXJQABE=[/D]
    [/RSAKeyValue]

    (data was broken to be more readable)

    I still can´t get this XML done in my programming but with the information you post it will be a snap thing to do. Thanks.

    PLEASE NOTE, LESS THAN AND GREATER THAN HAS BEEN REPLACE BY SQUARE BRACKETS, sorry for the inconvenience and the re-post

  10. Hi, and thanks alot!!

    After aply this great solution, coping the class, i´m sure i have a bug in my call, i recieve the error “Call to a member function GetIntBuffer() on a non-object” in the line $PrivateExponent = $bodyItems[3]->GetIntBuffer();

    A var_dump($bodyItems); show an array whith only 2 elements, my code is:


    //LLAVE INICIO
    $key='./../../PEMs/207111250s.key.pem';
    $fp = fopen($key, "rb");
    $textokey = (fread($fp, 8192));
    fclose($fp);
    //OBTENER XML DE LLAVE INICIO
    //Decode root sequence
    $body = new ASNValue;
    $DERtextokey = PemToDer($textokey);
    $body->Decode($DERtextokey);
    $bodyItems = $body->GetSequence();
    //Read key values:
    $Modulus = $bodyItems[1]->GetIntBuffer();
    $PublicExponent = $bodyItems[2]->GetInt();
    $PrivateExponent = $bodyItems[3]->GetIntBuffer();
    $Prime1 = $bodyItems[4]->GetIntBuffer();
    $Prime2 = $bodyItems[5]->GetIntBuffer();
    $Exponent1 = $bodyItems[6]->GetIntBuffer();
    $Exponent2 = $bodyItems[7]->GetIntBuffer();
    $Coefficient = $bodyItems[8]->GetIntBuffer();

    Any idea?? can you see my bug??

    Thanks again

    • Hi, Diego!

      Are you sure you’re opening a private key? This error could happen if you’re opening a public key as public keys have different structure and thus require different code to parse. I didn’t include that code into the post to make it shorter but I can look it up in my sources if needed.
      You can find out what’s the type of your key by looking into the PEM file. Private keys start from “—–BEGIN RSA PRIVATE KEY—–” line while public ones surely have “—–BEGIN RSA PUBLIC KEY—–“.
      Also the “fread($fp, 8192)” call may trim files longer than 8K. I suggest you use file_get_contents instead of fopen-fread-fclose.

      Hope that helps.

      • Thanks Anton

        Well, i did this:
        Changed the fread($fp, 8192) with file_get_contents… 🙁
        Pasted the pem file content in my code… 🙁

        Viewing the key.pem file i can see:

        —–BEGIN PRIVATE KEY—–
        MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJzTWRP5939YQBYg
        YxPip5F1QsoXGgrnH4Fcb8JJ8u1gPHxXi8bHN1jRvUBA9CeZGgdIkHfRffIVslBm
        ZGVDpNZFKuf8+1n9BdLJH/mr5JkYYySmzzRQ2iVx5qicwHxNaiP3rqvT5s5qBbDH
        mBRt75vlwtgXHxPw2V2tQqPUTrZNAgMBAAECgYB60JkmJ9BHjllB+sMcw5fvNiHE
        Gitu9mmZ6UYspbT1aTR1PCpe1YzmpfrL4xJSIHhd09ovu4QinhMzG7zsAPmJClCk…..
        —–END PRIVATE KEY—–

        Maybe the proble is some something about “BEGIN PRIVATE KEY” and “BEGIN RSA PRIVATE KEY” (RSA missing), the .key.pem file is working well signing other things. To be honest this .key.pem file is the result of an openssl key to pem conversion….

        Any other tip??

        Thanks

        • Hmm.. No, I don’t think it’s related to the file header/footer as they are getting removed anyway and if they aren’t removed, it is unlikely base64 decoding will work.
          I suggest you view DER version of the key (header/footer removed, base64 decoded) with ASN.1 editor I recommended in this post.

          I’ll email you so we can continue our discussion via email.

  11. Hey Anton,

    First of all, let me say THANKS! by your articles, I has been trying to achieve encoding from Windows C++ to PHP since quite long time, and I was lost in why it didnt seem to work, but after reading your articles, they gave me motivation to continue developing in the subject. Also, I would like to say THANKS to Jason by the PEM->BLOB conversion method.

    The other thing, I would likle to share is that, there seems to be a little problem with Jason’s method, I was googling and found another question in another website of a guy having problems converting from PEM to BLOB using Jason’s method described above and seems like a second call to “CryptDecodeObjectEx” gives this error:

    “ASN.1 bad tag value met”

    …After that comment, he posted his code, that SEEMS (because I didnt test it yet) to be the correct way to import a PEM key into Crypto context. Here is the link to that post:

    http://stackoverflow.com/a/3803333

    I’m posting this in case someone else is having that error, to bring in some “light” into that issue…

    Thanks

  12. Hey Anton,

    It’s me again….

    I have another question that I probably know the answer, but I would like to confirm that my answer is correct:

    1- Let’s say I create a PUBLIC key and export it to a PEM file using my PHP script.

    2- I download that PEM file from my web server into my computer

    3- Let’s say I would like to import that key into my Windows C++ application using Jason’s method.

    At this point should I convert from BIG endian to LITTLE endian?, prior calling the “CryptStringToBinary” API function

    OR!

    Does the “CryptStringToBinary” API function do that already?

    Thanks

  13. Thanks for the great post. Although i havent done much work in PHP but i have been assigned to convert a .NET Encryption Program to PHP.

    We have a PreAssigned Key and Data. In .NET we use RSACryptoServiceProvider provider and encrypt the data on this. I was reading and found that i could use OPENSSL to convert it in PHP but we do want a support for Windows Web Server as well Linux.

    Not sure how should i proceed. appreciate your help.

    • Hi!

      What format .NET uses to store RSA keys?
      I’m not sure what “we do want a support for Windows Web Server as well Linux” means exactly. PEM format works for PHP application on Windows and IIS as well as on Apache and Linux. It doesn’t depend on web server software or operating system.

      If you need to use the same keypair in various applications using different encryption libraries then you can either create duplicates of the keypair in different formats suitable for each library or you can use the same format (as we used DER) and add code to each applciation that will parse the key file and load key values to the encryption library used.

      Hope that helps.

      • Well Here is the .NET Script i have, which i need to convert to PHP

        using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider(0x400))
        {
        byte[] buffer2 = GetBytesFrom64StringUsingBase64(publicKeyAsString);
        provider.ImportCspBlob(buffer2);
        byte[] buffer4 = GetBytesFrom64StringUsingBase64(ConvertBytesToStringUsingBase64(GetBytesFromStringUsingUTF8(DataToEncrypt)));
        buffer = provider.Encrypt(buffer4, DoOAEPPadding);
        }

        The reason i mentioned about Windows and Linux was because i read somewhere that openssl1.0 is supported only on Linux platform or something.

        APpreciate your assistance.

        • Well, I suggest you refer to provider.ImportCspBlob() method description to find out what key format it uses. I’m not very familiar with .NET.

          The reason i mentioned about Windows and Linux was because i read somewhere that openssl1.0 is supported only on Linux platform or something.

          Perhaps, you read that from this blog post as it mentions there was no OpenSSL 1.x build for Windows at the time of post. But it was written long time ago and OpenSSL 1.x for Windows is now surely available: http://slproweb.com/products/Win32OpenSSL.html

          Moreover, please do not confuse OpenSSL command line execuable and OpenSSL PHP extension. Those are different things though based on the same library. PHP OpenSSL extension works fine on both Windows and Linux: http://php.net/manual/en/book.openssl.php.

Comments are closed.