如何在C#中获取RSACryptoServiceProvider公钥和私钥

4
我正在运行以下代码仅获取公钥和私钥,但似乎输出了整个XML格式。我只需要输出如公钥和私钥演示所示的密钥。
        static RSACryptoServiceProvider rsa;
        private RSAParameters _privateKey;
        private RSAParameters _publicKey;
        public RSACrypto()
        {
            rsa = new RSACryptoServiceProvider(2048);
            _privateKey = rsa.ExportParameters(true);
            _publicKey = rsa.ExportParameters(false);

        }
        public string GetPublicKeyString()
        {
            var sw = new StringWriter();
            var xs = new XmlSerializer(typeof(RSAParameters));
            xs.Serialize(sw, _publicKey);
            return sw.ToString();
        }
        public string GetPrivateKeyString()
        {
            var sw = new StringWriter();
            var xs = new XmlSerializer(typeof(RSAParameters));
            xs.Serialize(sw, _privateKey);
            return sw.ToString();
        }

请查看此问题的第二个答案。https://dev59.com/hmzXa4cB1Zd3GeqPUYf9 - NthDeveloper
@NthDeveloper:那个回答与这个问题无关。 - President James K. Polk
1
您可以使用Bouncycastle C#库,这是最简单的解决方案,或者您可以尝试手动编写ASN.1对象的生成代码,这是可行的,但要困难得多。 - President James K. Polk
在.NET Core 3.0(预览版1)中,您可以使用rsa.ExportSubjectPublicKeyInfo()和rsa.ExportRSAPrivateKey()获取二进制数据,我明天会发布答案。 - bartonjs
@JamesKPolk 谢谢,我使用了BouncyCastle C#库,请将其作为答案编写以关闭此线程。 - ARH
4个回答

20

.NET Core 3.0起,这已(在很大程度上)内置。

编写SubjectPublicKeyInfo和RSAPrivateKey

.NET Core 3.0 内置API

内置API的输出是二进制表示法,如果要使它们成为PEM,则需要输出标题、尾部和Base64。

private static string MakePem(byte[] ber, string header)
{
    StringBuilder builder = new StringBuilder("-----BEGIN ");
    builder.Append(header);
    builder.AppendLine("-----");

    string base64 = Convert.ToBase64String(ber);
    int offset = 0;
    const int LineLength = 64;

    while (offset < base64.Length)
    {
        int lineEnd = Math.Min(offset + LineLength, base64.Length);
        builder.AppendLine(base64.Substring(offset, lineEnd - offset));
        offset = lineEnd;
    }

    builder.Append("-----END ");
    builder.Append(header);
    builder.AppendLine("-----");
    return builder.ToString();
}

因此,生成这些字符串的方法为:

string publicKey = MakePem(rsa.ExportSubjectPublicKeyInfo(), "PUBLIC KEY");
string privateKey = MakePem(rsa.ExportRSAPrivateKey(), "RSA PRIVATE KEY");

半自动方式

如果您无法使用.NET Core 3.0,但可以使用预发布的NuGet包,您可以利用原型ASN.1编写器包(这是与.NET Core 3.0内部使用相同的代码,只是API表面不是最终版本)。

生成公钥:

private static string ToSubjectPublicKeyInfo(RSA rsa)
{
    RSAParameters rsaParameters = rsa.ExportParameters(false);

    AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
    writer.PushSequence();

    writer.PushSequence();
    writer.WriteObjectIdentifier("1.2.840.113549.1.1.1");
    writer.WriteNull();
    writer.PopSequence();

    AsnWriter innerWriter = new AsnWriter(AsnEncodingRules.DER);

    innerWriter.PushSequence();
    WriteRSAParameter(innerWriter, rsaParameters.Modulus);
    WriteRSAParameter(innerWriter, rsaParameters.Exponent);
    innerWriter.PopSequence();

    writer.WriteBitString(innerWriter.Encode());

    writer.PopSequence();
    return MakePem(writer.Encode(), "PUBLIC KEY");
}

制作私钥:

private static string ToRSAPrivateKey(RSA rsa)
{
    RSAParameters rsaParameters = rsa.ExportParameters(true);

    AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
    writer.PushSequence();

    writer.WriteInteger(0);
    WriteRSAParameter(writer, rsaParameters.Modulus);
    WriteRSAParameter(writer, rsaParameters.Exponent);
    WriteRSAParameter(writer, rsaParameters.D);
    WriteRSAParameter(writer, rsaParameters.P);
    WriteRSAParameter(writer, rsaParameters.Q);
    WriteRSAParameter(writer, rsaParameters.DP);
    WriteRSAParameter(writer, rsaParameters.DQ);
    WriteRSAParameter(writer, rsaParameters.InverseQ);

    writer.PopSequence();
    return MakePem(writer.Encode(), "RSA PRIVATE KEY");
}

读取它们

.NET Core 3.0内置API

除此之外,.NET Core 3.0无法理解PEM编码,所以您需要自己进行PEM->二进制转换:

private const string RsaPrivateKey = "RSA PRIVATE KEY";
private const string SubjectPublicKeyInfo = "PUBLIC KEY";

private static byte[] PemToBer(string pem, string header)
{
    // Technically these should include a newline at the end,
    // and either newline-or-beginning-of-data at the beginning.
    string begin = $"-----BEGIN {header}-----";
    string end = $"-----END {header}-----";

    int beginIdx = pem.IndexOf(begin);
    int base64Start = beginIdx + begin.Length;
    int endIdx = pem.IndexOf(end, base64Start);

    return Convert.FromBase64String(pem.Substring(base64Start, endIdx - base64Start));
}

完成这个步骤后,你现在可以加载密钥:

using (RSA rsa = RSA.Create())
{
    rsa.ImportRSAPrivateKey(PemToBer(pemPrivateKey, RsaPrivateKey), out _);

    ...
}

using (RSA rsa = RSA.Create())
{
    rsa.ImportSubjectPublicKeyInfo(PemToBer(pemPublicKey, SubjectPublicKeyInfo), out _);

    ...
}

半手动方式

如果您无法使用.NET Core 3.0,但可以使用预发布的NuGet软件包,则可以利用原型ASN.1读取器软件包(该软件包与.NET Core 3.0内部使用的代码相同;只是API表面尚未最终确定)。

对于公钥:

private static RSA FromSubjectPublicKeyInfo(string pem)
{
    AsnReader reader = new AsnReader(PemToBer(pem, SubjectPublicKeyInfo), AsnEncodingRules.DER);
    AsnReader spki = reader.ReadSequence();
    reader.ThrowIfNotEmpty();

    AsnReader algorithmId = spki.ReadSequence();

    if (algorithmId.ReadObjectIdentifierAsString() != "1.2.840.113549.1.1.1")
    {
        throw new InvalidOperationException();
    }

    algorithmId.ReadNull();
    algorithmId.ThrowIfNotEmpty();

    AsnReader rsaPublicKey = spki.ReadSequence();

    RSAParameters rsaParameters = new RSAParameters
    {
        Modulus = ReadNormalizedInteger(rsaPublicKey),
        Exponent = ReadNormalizedInteger(rsaPublicKey),
    };

    rsaPublicKey.ThrowIfNotEmpty();

    RSA rsa = RSA.Create();
    rsa.ImportParameters(rsaParameters);
    return rsa;
}

private static byte[] ReadNormalizedInteger(AsnReader reader)
{
    ReadOnlyMemory<byte> memory = reader.ReadIntegerBytes();
    ReadOnlySpan<byte> span = memory.Span;

    if (span[0] == 0)
    {
        span = span.Slice(1);
    }

    return span.ToArray();
}

由于私钥值必须具有正确大小的数组,因此私钥比较棘手:

private static RSA FromRSAPrivateKey(string pem)
{
    AsnReader reader = new AsnReader(PemToBer(pem, RsaPrivateKey), AsnEncodingRules.DER);
    AsnReader rsaPrivateKey = reader.ReadSequence();
    reader.ThrowIfNotEmpty();

    if (!rsaPrivateKey.TryReadInt32(out int version) || version != 0)
    {
        throw new InvalidOperationException();
    }

    byte[] modulus = ReadNormalizedInteger(rsaPrivateKey);
    int halfModulusLen = (modulus.Length + 1) / 2;

    RSAParameters rsaParameters = new RSAParameters
    {
        Modulus = modulus,
        Exponent = ReadNormalizedInteger(rsaPrivateKey),
        D = ReadNormalizedInteger(rsaPrivateKey, modulus.Length),
        P = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
        Q = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
        DP = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
        DQ = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
        InverseQ = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
    };

    rsaPrivateKey.ThrowIfNotEmpty();

    RSA rsa = RSA.Create();
    rsa.ImportParameters(rsaParameters);
    return rsa;
}

private static byte[] ReadNormalizedInteger(AsnReader reader, int length)
{
    ReadOnlyMemory<byte> memory = reader.ReadIntegerBytes();
    ReadOnlySpan<byte> span = memory.Span;

    if (span[0] == 0)
    {
        span = span.Slice(1);
    }

    byte[] buf = new byte[length];
    int skipSize = length - span.Length;
    span.CopyTo(buf.AsSpan(skipSize));
    return buf;
}

ASN读写器是否会包含在官方的NuGet中,以针对netstandard?不包括实验性的MyGet源。 - cleftheris
1
@cleftheris 目前还没有确切的消息。您可以在 https://github.com/dotnet/corefx/issues/21833 上表达特定的支持矩阵需求。 - bartonjs

3

Bouncycastle C#库有一些帮助类,可以让这个过程相对容易。不幸的是,文档写得不够清晰。以下是一个例子:

using System;
using System.IO;
using System.Security.Cryptography;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;

namespace ExportToStandardFormats
{
    class MainClass
    {

        public static void Main(string[] args)
        {
            var rsa = new RSACryptoServiceProvider(2048);
            var rsaKeyPair = DotNetUtilities.GetRsaKeyPair(rsa);
            var writer = new StringWriter();
            var pemWriter = new PemWriter(writer);
            pemWriter.WriteObject(rsaKeyPair.Public);
            pemWriter.WriteObject(rsaKeyPair.Private);
            Console.WriteLine(writer);
        }
    }
}

1

在上面的答案基础上,要将公钥和私钥分别以字符串格式获取,您可以使用以下代码片段。

public static void GenerateKeyPair()
{
  try
  {
    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
    var rsaKeyPair = DotNetUtilities.GetRsaKeyPair(rsa);
    
    //Getting publickey
    TextWriter textWriter = new StringWriter();
    PemWriter pemWriter = new PemWriter(textWriter);
    pemWriter.WriteObject(rsaKeyPair.Public);
    publicKey = textWriter.ToString();

    //Getting privatekey
    textWriter = new StringWriter();
    pemWriter = new PemWriter(textWriter);
    pemWriter.WriteObject(rsaKeyPair.Private);
    privateKey = textWriter.ToString();

    Console.WriteLine("public key, {0}", publicKey);
    Console.WriteLine("private key, {0}", privateKey);
  }
  catch (Exception e)
  {
    Console.WriteLine($"GenerateKeyPair Failed with {e}");
    Console.WriteLine(e);
  }
}

1

我希望将公钥和私钥提取为字符数组,而不是字符串。我找到了一种解决方案,这是对James和Vikram提供的答案进行修改得出的。对于寻找此类解决方案的人可能会有所帮助。

public static void GenerateKeyPair()
{
  char[] private_key= null;
  char[] public_key=null;
  RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
  var rsaKeyPair = DotNetUtilities.GetRsaKeyPair(rsa);

  //PrivateKey
  MemoryStream memoryStream = new MemoryStream();
  TextWriter streamWriter = new StreamWriter(memoryStream);
  PemWriter pemWriter = new PemWriter(streamWriter);
  pemWriter.WriteObject(rsaKeyPair.Private);
  streamWriter.Flush();
  byte[] bytearray = memoryStream.GetBuffer();
  private_key = Encoding.ASCII.GetChars(bytearray);

  //PublicKey
  memoryStream = new MemoryStream();
  streamWriter = new StreamWriter(memoryStream);
  pemWriter = new PemWriter(streamWriter);
  pemWriter.WriteObject(rsaKeyPair.Public);
  streamWriter.Flush();
  bytearray = memoryStream.GetBuffer();
  public_key = Encoding.ASCII.GetChars(bytearray);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接