C # Exportar clave RSA privada / pública de RSACryptoServiceProvider a cadena PEM

Tengo una instancia de System.Security.Cryptography.RSACryptoServiceProvider, necesito exportar su clave a una cadena PEM, como esta:

-----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----- 

Pero no hay tal opción según la documentación de MSDN, solo hay algún tipo de exportación XML. No puedo usar ninguna biblioteca de terceros como BouncyCastle. ¿Hay alguna manera de generar esta cadena?

Tenga en cuenta: el siguiente código es para exportar una clave privada . Si está buscando exportar la clave pública , consulte mi respuesta aquí .

El formato PEM es simplemente la encoding ASN.1 DER de la clave (por PKCS # 1 ) convertida a Base64. Dado el número limitado de campos necesarios para representar la clave, es bastante sencillo crear un codificador DER rápido y sucio para generar el formato adecuado y luego Base64 codificarlo. Como tal, el código que sigue no es particularmente elegante, pero cumple su función:

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

Para exportar PublicKey use este código:

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

Para cualquier otra persona que se resistió a la aparente complejidad de la respuesta original (que es muy útil, no me malinterprete), pensé en publicar mi solución, que es un poco más directo IMO (pero aún basado en la respuesta original):

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