57

I have an instance of System.Security.Cryptography.RSACryptoServiceProvider, i need to export it's key to a PEM string - like this:

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDUNPB6Lvx+tlP5QhSikADl71AjZf9KN31qrDpXNDNHEI0OTVJ1
OaP2l56bSKNo8trFne1NK/B4JzCuNP8x6oGCAG+7bFgkbTMzV2PCoDCRjNH957Q4
Gxgx1VoS6PjD3OigZnx5b9Hebbp3OrTuqNZaK/oLPGr5swxHILFVeHKupQIDAQAB
AoGAQk3MOZEGyZy0fjQ8eFKgRTfSBU1wR8Mwx6zKicbAotq0CBz2v7Pj3D+higlX
LYp7+rUOmUc6WoB8QGJEvlb0YZVxUg1yDLMWYPE7ddsHsOkBIs7zIyS6cqhn0yZD
VTRFjVST/EduvpUOL5hbyLSwuq+rbv0iPwGW5hkCHNEhx2ECQQDfLS5549wjiFXF
gcio8g715eMT+20we3YmgMJDcviMGwN/mArvnBgBQsFtCTsMoOxm68SfIrBYlKYy
BsFxn+19AkEA82q83pmcbGJRJ3ZMC/Pv+/+/XNFOvMkfT9qbuA6Lv69Z1yk7I1ie
FTH6tOmPUu4WsIOFtDuYbfV2pvpqx7GuSQJAK3SnvRIyNjUAxoF76fGgGh9WNPjb
DPqtSdf+e5Wycc18w+Z+EqPpRK2T7kBC4DWhcnTsBzSA8+6V4d3Q4ugKHQJATRhw
a3xxm65kD8CbA2omh0UQQgCVFJwKy8rsaRZKUtLh/JC1h1No9kOXKTeUSmrYSt3N
OjFp7OHCy84ihc8T6QJBANe+9xkN9hJYNK1pL1kSwXNuebzcgk3AMwHh7ThvjLgO
jruxbM2NyMM5tl9NZCgh1vKc2v5VaonqM1NBQPDeTTw=
-----END RSA PRIVATE KEY-----

But there is no such option according to the MSDN documentation, there is only some kind of XML export. I can't use any third party libraries like BouncyCastle. Is there any way to generate this string?

2
  • How and where does an instance of that class have a key? Commented May 19, 2014 at 10:27
  • 2
    Tthe pain point is due to .Net and their use of XML encoding from RFC 3275. .Net does not use ASN.1/DER or PEM encoded keys. I think its the only crypto library that does things this way. Commented Feb 11, 2017 at 14:41

6 Answers 6

95

Please note: The code below is for exporting a private key. If you are looking to export the public key, please refer to my answer given here.

The PEM format is simply the ASN.1 DER encoding of the key (per PKCS#1) converted to Base64. Given the limited number of fields needed to represent the key, it's pretty straightforward to create quick-and-dirty DER encoder to output the appropriate format then Base64 encode it. As such, the code that follows is not particularly elegant, but does the job:

private static void ExportPrivateKey(RSACryptoServiceProvider csp, TextWriter outputStream)
{
    if (csp.PublicOnly) throw new ArgumentException("CSP does not contain a private key", "csp");
    var parameters = csp.ExportParameters(true);
    using (var stream = new MemoryStream())
    {
        var writer = new BinaryWriter(stream);
        writer.Write((byte)0x30); // SEQUENCE
        using (var innerStream = new MemoryStream())
        {
            var innerWriter = new BinaryWriter(innerStream);
            EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version
            EncodeIntegerBigEndian(innerWriter, parameters.Modulus);
            EncodeIntegerBigEndian(innerWriter, parameters.Exponent);
            EncodeIntegerBigEndian(innerWriter, parameters.D);
            EncodeIntegerBigEndian(innerWriter, parameters.P);
            EncodeIntegerBigEndian(innerWriter, parameters.Q);
            EncodeIntegerBigEndian(innerWriter, parameters.DP);
            EncodeIntegerBigEndian(innerWriter, parameters.DQ);
            EncodeIntegerBigEndian(innerWriter, parameters.InverseQ);
            var length = (int)innerStream.Length;
            EncodeLength(writer, length);
            writer.Write(innerStream.GetBuffer(), 0, length);
        }

        var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
        outputStream.WriteLine("-----BEGIN RSA PRIVATE KEY-----");
        // Output as Base64 with lines chopped at 64 characters
        for (var i = 0; i < base64.Length; i += 64)
        {
            outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
        }
        outputStream.WriteLine("-----END RSA PRIVATE KEY-----");
    }
}

private static void EncodeLength(BinaryWriter stream, int length)
{
    if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
    if (length < 0x80)
    {
        // Short form
        stream.Write((byte)length);
    }
    else
    {
        // Long form
        var temp = length;
        var bytesRequired = 0;
        while (temp > 0)
        {
            temp >>= 8;
            bytesRequired++;
        }
        stream.Write((byte)(bytesRequired | 0x80));
        for (var i = bytesRequired - 1; i >= 0; i--)
        {
            stream.Write((byte)(length >> (8 * i) & 0xff));
        }
    }
}

private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
{
    stream.Write((byte)0x02); // INTEGER
    var prefixZeros = 0;
    for (var i = 0; i < value.Length; i++)
    {
        if (value[i] != 0) break;
        prefixZeros++;
    }
    if (value.Length - prefixZeros == 0)
    {
        EncodeLength(stream, 1);
        stream.Write((byte)0);
    }
    else
    {
        if (forceUnsigned && value[prefixZeros] > 0x7f)
        {
            // Add a prefix zero to force unsigned if the MSB is 1
            EncodeLength(stream, value.Length - prefixZeros + 1);
            stream.Write((byte)0);
        }
        else
        {
            EncodeLength(stream, value.Length - prefixZeros);
        }
        for (var i = prefixZeros; i < value.Length; i++)
        {
            stream.Write(value[i]);
        }
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

Great, this works perfectly, but how do I export the public key. Does it have similar structure. I've tried doing this with just the exponent and modulus (the contents of the public key) but it doesn't return a valid result. How to get the public key string?
Never mind. I got it working, I forgot to remove the version part. Now it also exports public keys.
For those interested, correctly exporting a public key can be done via the code in my answer here: stackoverflow.com/questions/28406888/… which re-uses some of the methods from this answer.
This works great for exporting both private key and public key to pem file
I've compiled and slightly modified Iridium's two excellent export functions and combined them with import functions for a full solution (import and export both public and private keys): Import and export RSA Keys between C# and PEM format using BouncyCastle
|
20

With the current version of .NET, this can be done in a simple way.


  RSA rsa = RSA.Create();
  rsa.KeySize = 4096;

  // Private key export.
  string hdrPrv = "-----BEGIN RSA PRIVATE KEY-----";
  string ftrPrv = "-----END RSA PRIVATE KEY-----";
  string keyPrv = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey());
  string PEMPrv = $"{hdrPrv}\n{keyPrv}\n{ftrPrv}";//concatenate header and footer to actual key


  // Public key export (with a correction for more accuracy from RobSiklos's comment to have the key in PKCS#1 RSAPublicKey format)
  string hdrPub = "-----BEGIN RSA PUBLIC KEY-----";
  string ftrPub = "-----END RSA PUBLIC KEY-----";
  string keyPub = Convert.ToBase64String(rsa.ExportRSAPublicKey());
  string PEMPub = $"{hdrPub}\n{keyPub}\n{ftrPub}";//concatenate header and footer to actual key
  
  // Distribute PEMs.

Note: To have the nicely formatted file with new lines, you can write a little function to do it for you. With the solution given above, you will have a file with only three lines.

3 Comments

In your comment for exporting public key you say to use hdr and ftr but those values are specific to the private key.... should be hdr="-----BEGIN RSA PUBLIC KEY-----" and ftr="-----END RSA PUBLIC KEY-----"
perhaps I wrote this bad, but saying update hdr and ftr in meant exactly what you said. :)
This is slightly incorrect. When using ExportSubjectPublicKeyInfo() the header/footer should contain "PUBLIC KEY" not "RSA PUBLIC KEY". If you want to use the latter, then use ExportRSAPublicKey() instead of ExportSubjectPublicKeyInfo()
13

If you're using .NET Core 3.0 this is already implemented out of the box

    public string ExportPrivateKey(RSA rsa)
    {
        var privateKeyBytes = rsa.ExportRSAPrivateKey();
        var builder = new StringBuilder("-----BEGIN RSA PRIVATE KEY");
        builder.AppendLine("-----");

        var base64PrivateKeyString = Convert.ToBase64String(privateKeyBytes);
        var offset = 0;
        const int LINE_LENGTH = 64;

        while (offset < base64PrivateKeyString.Length)
        {
            var lineEnd = Math.Min(offset + LINE_LENGTH, base64PrivateKeyString.Length);
            builder.AppendLine(base64PrivateKeyString.Substring(offset, lineEnd - offset));
            offset = lineEnd;
        }

        builder.Append("-----END RSA PRIVATE KEY");
        builder.AppendLine("-----");
        return builder.ToString();
    }

3 Comments

This code worked just fine for me - BUT is has a small bug: The opening line states "BEGIN RSA PRIVATE KEY" (which is correct), but the closing line omits the "RSA" -> closing line should be "END RSA PRIVATE KEY"
Hi @Prasanth, can you please share the details about where it is pointed that RSACryptoServiceProvider in .NET Core is Windows specific?
I think, RSACryptoServiceProvider is not windows specific. Windows CSP (CAPI) is windows specific. learn.microsoft.com/en-us/dotnet/api/…
12

Here is a GREAT NEWS, with .NET 7, Microsoft has added a new method to export RSA keys directly to PEM format.

Refer RSA.ExportRSAPrivateKeyPem Method

Below is how you can use it.

using (var rsa = new RSACryptoServiceProvider(2048)) // Generate a new 2048 bit RSA key
{
    // RSA keys in PKCS#1 format, PEM encoded
    string publicPrivateKeyPEM = rsa.ExportRSAPrivateKeyPem();
    string publicOnlyKeyPEM = rsa.ExportRSAPublicKeyPem();

    // RSA keys in XML format
    string publicPrivateKeyXML = rsa.ToXmlString(true);
    string publicOnlyKeyXML = rsa.ToXmlString(false);

    // RSA keys in byte array
    byte[] publicPrivateKey = rsa.ExportRSAPrivateKey();
    byte[] publicOnlyKey = rsa.ExportRSAPublicKey();
}

Comments

3

For anyone else who balked at the original answer's apparent complexity (which is very helpful, don't get me wrong), I thought I'd post my solution which is a little more straightforward IMO (but still based on the original answer):

public class RsaCsp2DerConverter {
   private const int MaximumLineLength = 64;

   // Based roughly on: http://stackoverflow.com/a/23739932/1254575

   public RsaCsp2DerConverter() {

   }

   public byte[] ExportPrivateKey(String cspBase64Blob) {
      if (String.IsNullOrEmpty(cspBase64Blob) == true)
         throw new ArgumentNullException(nameof(cspBase64Blob));

      var csp = new RSACryptoServiceProvider();

      csp.ImportCspBlob(Convert.FromBase64String(cspBase64Blob));

      if (csp.PublicOnly)
         throw new ArgumentException("CSP does not contain a private key!", nameof(csp));

      var parameters = csp.ExportParameters(true);

      var list = new List<byte[]> {
         new byte[] {0x00},
         parameters.Modulus,
         parameters.Exponent,
         parameters.D,
         parameters.P,
         parameters.Q,
         parameters.DP,
         parameters.DQ,
         parameters.InverseQ
      };

      return SerializeList(list);
   }

   private byte[] Encode(byte[] inBytes, bool useTypeOctet = true) {
      int length = inBytes.Length;
      var bytes = new List<byte>();

      if (useTypeOctet == true)
         bytes.Add(0x02); // INTEGER

      bytes.Add(0x84); // Long format, 4 bytes
      bytes.AddRange(BitConverter.GetBytes(length).Reverse());
      bytes.AddRange(inBytes);

      return bytes.ToArray();
   }

   public String PemEncode(byte[] bytes) {
      if (bytes == null)
         throw new ArgumentNullException(nameof(bytes));

      var base64 = Convert.ToBase64String(bytes);

      StringBuilder b = new StringBuilder();
      b.Append("-----BEGIN RSA PRIVATE KEY-----\n");

      for (int i = 0; i < base64.Length; i += MaximumLineLength)
         b.Append($"{ base64.Substring(i, Math.Min(MaximumLineLength, base64.Length - i)) }\n");

      b.Append("-----END RSA PRIVATE KEY-----\n");

      return b.ToString();
   }

   private byte[] SerializeList(List<byte[]> list) {
      if (list == null)
         throw new ArgumentNullException(nameof(list));

      var keyBytes = list.Select(e => Encode(e)).SelectMany(e => e).ToArray();

      var binaryWriter = new BinaryWriter(new MemoryStream());
      binaryWriter.Write((byte) 0x30); // SEQUENCE
      binaryWriter.Write(Encode(keyBytes, false));
      binaryWriter.Flush();

      var result = ((MemoryStream) binaryWriter.BaseStream).ToArray();

      binaryWriter.BaseStream.Dispose();
      binaryWriter.Dispose();

      return result;
   }
}

Comments

-1
 public static Func<string, string> ToBase64PemFromKeyXMLString= (xmlPrivateKey) =>
        {
            if (string.IsNullOrEmpty(xmlPrivateKey))
                throw new ArgumentNullException("RSA key must contains value!");
            var keyContent = new PemReader(new StringReader(xmlPrivateKey));
            if (keyContent == null)
                throw new ArgumentNullException("private key is not valid!");
            var ciphrPrivateKey = (AsymmetricCipherKeyPair)keyContent.ReadObject();
            var asymmetricKey = new AsymmetricKeyEntry(ciphrPrivateKey.Private);

            PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(asymmetricKey.Key);
            var serializedPrivateKey = privateKeyInfo.ToAsn1Object().GetDerEncoded();
            return Convert.ToBase64String(serializedPrivateKey);
        };

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.