Convert RSA public/private key from XML to PEM format (.NET) (Part 2)
In my previous post I’ve shown how to convert the public key of an XML formatted RSA key to the more widely used PEM format. The only limitation of the solution was that since it utilizes the Cryptographic Next Generation (CNG) algorithms it is usable only on Windows 7 and Windows Server 2008 R2.
So bellow I’ll demonstrate a solution that works under all operating systems. Also as an extra the solution bellow can convert the private key as well 😉 Both the public and the private keys exported by the functions bellow are parsed by OpenSSL!
You can find the compiled source here.
Enjoy!
C#
private static byte[] RSA_OID =
{ 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 }; // Object ID for RSA
// Corresponding ASN identification bytes
const byte INTEGER = 0x2;
const byte SEQUENCE = 0x30;
const byte BIT_STRING = 0x3;
const byte OCTET_STRING = 0x4;
private static string ConvertPublicKey(RSAParameters param)
{
List<byte> arrBinaryPublicKey = new List<byte>();
arrBinaryPublicKey.InsertRange(0, param.Exponent);
arrBinaryPublicKey.Insert(0, (byte)arrBinaryPublicKey.Count);
arrBinaryPublicKey.Insert(0, INTEGER);
arrBinaryPublicKey.InsertRange(0, param.Modulus);
AppendLength(ref arrBinaryPublicKey, param.Modulus.Length);
arrBinaryPublicKey.Insert(0, INTEGER);
AppendLength(ref arrBinaryPublicKey, arrBinaryPublicKey.Count);
arrBinaryPublicKey.Insert(0, SEQUENCE);
arrBinaryPublicKey.Insert(0, 0x0); // Add NULL value
AppendLength(ref arrBinaryPublicKey, arrBinaryPublicKey.Count);
arrBinaryPublicKey.Insert(0, BIT_STRING);
arrBinaryPublicKey.InsertRange(0, RSA_OID);
AppendLength(ref arrBinaryPublicKey, arrBinaryPublicKey.Count);
arrBinaryPublicKey.Insert(0, SEQUENCE);
return System.Convert.ToBase64String(arrBinaryPublicKey.ToArray());
}
private static string ConvertPrivateKey(RSAParameters param)
{
List<byte> arrBinaryPrivateKey = new List<byte>();
arrBinaryPrivateKey.InsertRange(0, param.InverseQ);
AppendLength(ref arrBinaryPrivateKey, param.InverseQ.Length);
arrBinaryPrivateKey.Insert(0, INTEGER);
arrBinaryPrivateKey.InsertRange(0, param.DQ);
AppendLength(ref arrBinaryPrivateKey, param.DQ.Length);
arrBinaryPrivateKey.Insert(0, INTEGER);
arrBinaryPrivateKey.InsertRange(0, param.DP);
AppendLength(ref arrBinaryPrivateKey, param.DP.Length);
arrBinaryPrivateKey.Insert(0, INTEGER);
arrBinaryPrivateKey.InsertRange(0, param.Q);
AppendLength(ref arrBinaryPrivateKey, param.Q.Length);
arrBinaryPrivateKey.Insert(0, INTEGER);
arrBinaryPrivateKey.InsertRange(0, param.P);
AppendLength(ref arrBinaryPrivateKey, param.P.Length);
arrBinaryPrivateKey.Insert(0, INTEGER);
arrBinaryPrivateKey.InsertRange(0, param.D);
AppendLength(ref arrBinaryPrivateKey, param.D.Length);
arrBinaryPrivateKey.Insert(0, INTEGER);
arrBinaryPrivateKey.InsertRange(0, param.Exponent);
AppendLength(ref arrBinaryPrivateKey, param.Exponent.Length);
arrBinaryPrivateKey.Insert(0, INTEGER);
arrBinaryPrivateKey.InsertRange(0, param.Modulus);
AppendLength(ref arrBinaryPrivateKey, param.Modulus.Length);
arrBinaryPrivateKey.Insert(0, INTEGER);
arrBinaryPrivateKey.Insert(0, 0x00);
AppendLength(ref arrBinaryPrivateKey, 1);
arrBinaryPrivateKey.Insert(0, INTEGER);
AppendLength(ref arrBinaryPrivateKey, arrBinaryPrivateKey.Count);
arrBinaryPrivateKey.Insert(0, SEQUENCE);
return System.Convert.ToBase64String(arrBinaryPrivateKey.ToArray());
}
private static void AppendLength(ref List<byte> arrBinaryData, int nLen)
{
if (nLen <= byte.MaxValue)
{
arrBinaryData.Insert(0, Convert.ToByte(nLen));
arrBinaryData.Insert(0, 0x81); //This byte means that the length fits in one byte
}
else
{
arrBinaryData.Insert(0, Convert.ToByte(nLen % (byte.MaxValue + 1)));
arrBinaryData.Insert(0, Convert.ToByte(nLen / (byte.MaxValue + 1)));
arrBinaryData.Insert(0, 0x82); //This byte means that the length fits in two byte
}
}
Hi Peter, excellent one, that is what I looked for.
But look here:
http://www.jensign.com/opensslkey/opensslkey.cs and search for "if first byte (highest order) of modulus is zero, don't include it"
and here:
osdir.com/ml/encryption.bouncy-castle.devel/2006-09/msg00035.html
So instead of this line:
AppendLength(ref arrBinaryPublicKey, param.Modulus.Length);
I had to go:
arrBinaryPublicKey.Insert(0, 0x0);
AppendLength(ref arrBinaryPublicKey, param.Modulus.Length+1);
And my complete tests PEM -> CER -> PEM are consistent.
I had this with 1024 bit key.
Miro
Hi Miro,
Yes, I know about the leading null value. Problem is that it is not consistent for all key lengths (I think 512 and 2048 keys do not have that null value). And since I did not found a pattern when and when not to include this, I do not add the null byte at all. Nevertheless even w/o the null byte they exported keys are parsed successfully by OpenSSL. So from what I see that null byte is an optional value for some of the keys. But note that because OpenSSL produces they keys with the null byte if you import and then export the key in OpenSSL the PEM string will be different than the one initially imported.
Peter
I have a problem with your code,..
the openssl.exe give me this:
—–BEGIN PRIVATE KEY—–
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOWsj2W …
your code this:
—–BEGIN PRIVATE KEY—–MIICXwKBAQACgYDlrI9loozd+UcW7YHtqJimQjzX9wHIUcc1KZyBBB8/5fZsgZ/smWS4Sd6HnPs9GSTtnTmM4bEgx28N3ulUshaaBEtZo3tsjwkBV/yVQ3SRyMDkqBA2NEjbcum
Im testing the "5ayPZaKM3flHFu…" in a web site "https://superdry.apphb.com/tools/online-rsa-key-converter" and it works fine