2014-05-19 13 views
22

mam instancję System.Security.Cryptography.RSACryptoServiceProvider, trzeba wyeksportować to klucz do łańcucha PEM - tak:klawisz C# Export prywatny/publiczny RSA z RSACryptoServiceProvider do PEM ciąg

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

Ale nie ma takiej opcji zgodnie z dokumentacją MSDN, istnieje tylko pewien rodzaj eksportu XML. Nie mogę używać żadnych bibliotek stron trzecich, takich jak BouncyCastle. Czy istnieje sposób na wygenerowanie tego ciągu?

+0

Jak i gdzie n instancja tej klasy ma klucz? – rene

+1

Ból jest spowodowany .Net i ich wykorzystaniem kodowania XML z [RFC 3275] (https://www.ietf.org/rfc/rfc3275.txt). .Net nie używa kluczy kodowanych ASN.1/DER lub PEM. Myślę, że jest to jedyna biblioteka kryptograficzna, która robi rzeczy w ten sposób. – jww

Odpowiedz

42

Uwaga: poniższy kod służy do eksportowania prywatnego klucza . Jeśli chcesz wyeksportować klucz publiczny , zapoznaj się z moją odpowiedzią podaną here.

Format PEM to po prostu kodowanie DER ASN.1 klucza (na PKCS#1) przekonwertowane do Base64. Biorąc pod uwagę ograniczoną liczbę pól potrzebnych do przedstawienia klucza, bardzo proste jest stworzenie szybkiego i brudnego enkodera DER, który wyprowadzi odpowiedni format, a następnie zakoduje go kod Base64. Jako taki, kod, który następuje nie jest szczególnie elegancki, ale spełnia swoje zadanie:

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

Świetnie, to działa idealnie, ale jak wyeksportować klucz publiczny. Czy ma podobną strukturę. Próbowałem zrobić to tylko z wykładnikiem i modułem (zawartość klucza publicznego), ale nie zwraca poprawnego wyniku. Jak zdobyć ciąg klucza publicznego? – nidzo732

+1

Nieważne. Mam to działa, zapomniałem usunąć część wersji. Teraz eksportuje również klucze publiczne. – nidzo732

+6

Dla zainteresowanych, prawidłowy eksport klucza publicznego można wykonać za pomocą kodu w mojej odpowiedzi tutaj: http://stackoverflow.com/questions/28406888/c-sharp-rsa-public-key-output-not-correct/28407693 # 28407693, który ponownie wykorzystuje niektóre metody z tej odpowiedzi. – Iridium

6

do eksportowania PublicKey użyć tego kodu:

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

Musiałem usunąć 'EncodeIntegerBigEndian (internalWriter, new byte [] {0x00}); // Version' line, aby móc załadować wygenerowany plik PEM w [Crypto :: RSAKey] (http://pocoproject.org/docs/Poco.Crypto.RSAKey.html) biblioteki Poco. – Isaac

+7

To nie działa dla mnie. Zamiast tego użyłem innego @ Iridium's inny post http://stackoverflow.com/questions/28406888/c-sharp-rsa-public-key-output-not-correct/28407693#28407693 jak na swoje komentarze poniżej jego odpowiedzi. –

+1

Co do diabła to sześć dodatkowych zapisów ... Klucz publiczny RSA to para '{n, e}'. Powinieneś sprawdzić PKCS # 1 dla poprawnego formatu na drucie, zamiast udostępniać hacki, jak wyżej. – jww

1

Dla każdego, kto powstrzymały się od oryginalnego odpowiedź na pozornej złożoności (co jest bardzo pomocne, nie zrozum mnie źle), myślałem, że opublikuję rozwiązanie, które jest trochę prostsze IMO (ale wciąż oparte na oryginalnej odpowiedzi):

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; 
    } 
} 
Powiązane problemy