2014-05-19 95 views
22

我有System.Security.Cryptography.RSACryptoServiceProvider的實例,我需要出口這是關鍵,PEM的字符串 - 這樣的:C#導出私鑰/ RSA公鑰到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。 有沒有什麼辦法可以產生這個字符串?

+0

如何以及在哪裏做了那個類的實例有一個鍵? – rene

+1

這個痛點來源於.Net及其[RFC 3275](https://www.ietf.org/rfc/rfc3275.txt)中XML編碼的使用。 .Net不使用ASN.1/DER或PEM編碼密鑰。我認爲它是唯一能以這種方式執行任務的加密庫。 – jww

回答

42

請注意:下面的代碼是用於導出私鑰密鑰。如果您想要輸出公鑰密鑰,請參閱我給出的回答here

該PEM格式僅僅是ASN.1 DER編碼的密鑰(每PKCS#1)轉換爲Base64。鑑於代表密鑰所需的字段數量有限,創建快速和簡潔的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]); 
     } 
    } 
} 
+0

太好了,這個工作很完美,但我如何導出公鑰。它有相似的結構嗎?我已經試過用指數和模數(公鑰的內容)來做這件事,但它沒有返回有效的結果。如何獲取公鑰字符串? – nidzo732

+1

沒關係。我得到它的工作,我忘了刪除版本的一部分。現在它也輸出公鑰。 – nidzo732

+6

對於那些感興趣的,正確的導出公鑰可以通過我的答案中的代碼來完成:http://stackoverflow.com/questions/28406888/c-sharp-rsa-public-key-output-not-correct/28407693 #28407693其中重新使用這個答案的一些方法。 – Iridium

6

對於出口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)); 
     } 
    } 
} 
+0

我不得不刪除'EncodeIntegerBigEndian(innerWriter,new byte [] {0x00}); // Version'行,以便能夠在Poco庫的[Crypto :: RSAKey](http://pocoproject.org/docs/Poco.Crypto.RSAKey.html)中加載生成的PEM文件。 – Isaac

+7

這不適合我。相反,我使用@銥星的其他帖子http://stackoverflow.com/questions/28406888/c-sharp-rsa-public-key-output-not-correct/28407693#28407693根據他的回答下他的評論。 –

+1

這六個額外的寫入是什麼......一個RSA公鑰是'{n,e}'對。您應該檢查PKCS#1的正確格式,而不是像上面那樣分享黑客。 – jww

1

對於別人誰在原來的答案是顯而易見的複雜性猶豫不決(這是非常有幫助的,不要誤會我的意思),我想我會後我的解決方案這是一個小更直接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<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; 
    } 
}