C#将RSACryptoServiceProvider中的私有/公共RSA密钥导出为PEM字符串

我有一个System.Security.Cryptography.RSACryptoServiceProvider的实例,我需要将它的密钥导出到PEM字符串 – 如下所示:

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

但根据MSDN文档,没有这样的选项,只有某种XML导出。 我不能使用任何第三方库,如BouncyCastle。 有没有办法生成这个字符串?

请注意:以下代码用于导出私钥 如果您要导出公钥,请参阅此处给出的答案。

PEM格式只是转换为Base64的密钥(每个PKCS#1 )的ASN.1 DER编码。 鉴于表示密钥所需的字段数量有限,创建快速而脏的DER编码器以输出适当的格式然后Base64对其进行编码非常简单。 因此,后面的代码不是特别优雅,但是做的工作是:

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

要导出PublicKey使用以下代码:

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

对于那些拒绝原始答案明显复杂性的人(这非常有帮助,不要误解我),我想我会发布我的解决方案,这是一个更直接的IMO(但仍然基于原始答案):

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