C# Export Private/Public RSA key from RSACryptoServiceProvider to PEM string

假如想象 提交于 2019-11-26 14:24:17

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]);
        }
    }
}
abasan

For exporting PublicKey use this code:

public static String ExportPublicKeyToPEMFormat(RSACryptoServiceProvider csp)
{
    TextWriter outputStream = new StringWriter();

    var parameters = csp.ExportParameters(false);
    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);

            //All Parameter Must Have Value so Set Other Parameter Value Whit Invalid Data  (for keeping Key Structure  use "parameters.Exponent" value for invalid data)
            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.D
            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.P
            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.Q
            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.DP
            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.DQ
            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of 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 PUBLIC 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 PUBLIC KEY-----");

        return outputStream.ToString();

    }
}

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]);
        }
    }
}

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));
        }
    }
}

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;
   }
}
 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);
        };
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!