2014-05-19 9 views
22

Ich habe eine Instanz von System.Security.Cryptography.RSACryptoServiceProvider, ich muss eine PEM-Zeichenfolge es ist Schlüssel zu exportieren - wie folgt aus:C# Export Private/Public-RSA-Schlüssel aus RSACryptoServiceProvider zu PEM-String

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

Aber Es gibt keine solche Option gemäß der MSDN-Dokumentation, es gibt nur eine Art von XML-Export. Ich kann keine Bibliotheken von Drittanbietern wie BouncyCastle verwenden. Gibt es eine Möglichkeit, diese Zeichenfolge zu generieren?

+0

Wie und wo kommt eine n Instanz dieser Klasse haben einen Schlüssel? – rene

+1

Der Schmerzpunkt ist aufgrund. NET und ihre Verwendung von XML-Codierung aus [RFC 3275] (https://www.ietf.org/rfc/rfc3275.txt). .Net verwendet keine ASN.1/DER- oder PEM-codierten Schlüssel. Ich denke, es ist die einzige Krypto-Bibliothek, die so funktioniert. – jww

Antwort

42

Bitte beachten Sie: Der folgende Code dient zum Exportieren eines privaten Schlüssels. Wenn Sie den öffentlichen Schlüssel exportieren möchten, beziehen Sie sich bitte auf meine Antwort gegeben here.

Das PEM-Format ist einfach die ASN.1 DER-Codierung des Schlüssels (per PKCS#1) konvertiert in Base64. Angesichts der begrenzten Anzahl von Feldern, die zum Darstellen des Schlüssels benötigt werden, ist es ziemlich einfach, einen schnell-und-schmutzigen DER-Codierer zu erzeugen, um das geeignete Format auszugeben, und dann Base64 zu codieren. Als solche, die der Code folgt, ist nicht besonders elegant, aber macht den 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]); 
     } 
    } 
} 
+0

Großartig, das funktioniert perfekt, aber wie exportiere ich den öffentlichen Schlüssel. Hat es eine ähnliche Struktur? Ich habe versucht, dies nur mit dem Exponenten und Modulus (der Inhalt des öffentlichen Schlüssels) zu tun, aber es gibt kein gültiges Ergebnis zurück. Wie erhält man die öffentliche Schlüsselzeichenfolge? – nidzo732

+1

Macht nichts. Ich habe es funktioniert, ich habe vergessen, den Versionsteil zu entfernen. Jetzt exportiert es auch öffentliche Schlüssel. – nidzo732

+6

Für diejenigen, die interessiert sind, kann ein öffentlicher Schlüssel korrekt über den Code in meiner Antwort hier: http://stackoverflow.com/questions/28406888/c-sharp-rs-a-ublic-key-output-not-correct/28407693 exportiert werden # 28407693, die einige der Methoden aus dieser Antwort wiederverwendet. – Iridium

6

Für PublicKey Gebrauch diesen Code exportieren:

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

Ich musste 'EncodeIntegerBigEndian (innerWriter, neues Byte [] {0x00}) entfernen; // Version'-Zeile, um die generierte PEM-Datei in [Crypto :: RSAKey] (http://pocoproject.org/docs/Poco.Crypto.RSAKey.html) der Poco-Bibliothek laden zu können. – Isaac

+7

Das hat nicht für mich funktioniert. Stattdessen habe ich @ Iridiums anderen Beitrag http://stackoverflow.com/questions/28406888/c-sharp-rsa-public-key-output-not-correct/28407693#28407693 gemäß seinen Kommentaren unterhalb seiner Antwort verwendet. –

+1

Was zur Hölle sind die sechs zusätzlichen Schreibvorgänge ... Ein öffentlicher RSA-Schlüssel ist das Paar '{n, e}'. Sie sollten PKCS # 1 für das korrekte Format auf der Leitung überprüfen, anstatt Hacks wie oben zu teilen. – jww

1

Für alle anderen, die in der ursprünglichen Antwort der scheinbaren Komplexität sträubte (was sehr hilfreich ist, verstehen Sie mich nicht falsch), ich dachte, dass ich meine Lösung veröffentlichen würde, die IMO ein wenig einfacher ist (aber immer noch auf der Grundlage der ursprünglichen Antwort):

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; 
    } 
} 
Verwandte Themen