【问题标题】:How to get RSACryptoServiceProvider public and private key only in c#如何仅在 C# 中获取 RSACryptoServiceProvider 公钥和私钥
【发布时间】:2019-05-28 14:28:28
【问题描述】:

我在代码下方运行仅获取公钥和私钥,但它似乎输出了整个 XML 格式。我只需要输出Public and Private Key demo中所示的键

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

【问题讨论】:

  • 看这个问题的第二个答案。 stackoverflow.com/questions/14047532/…
  • @NthDeveloper:这个答案与这个问题无关。
  • 您可以使用 Bouncycastle C# 库,这是最简单的解决方案,或者您可以尝试自己编写 ASN.1 对象的生成代码,这可行但要困难得多。
  • 在 .NET Core 3.0(预览版 1)中,您可以使用 rsa.ExportSubjectPublicKeyInfo() 和 rsa.ExportRSAPrivateKey() 获取二进制数据,我将在明天发布答案。
  • @JamesKPolk 谢谢,我使用了 BouncyCastle C# 库,请将其写为关闭此线程的答案。

标签: c# asp.net rsa public-key-encryption


【解决方案1】:

加上上面的答案,要分别获取字符串格式的公钥和私钥,可以使用下面的代码sn-p。

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

【讨论】:

    【解决方案2】:

    我想将公钥和私钥提取为 char 数组,而不是字符串。我找到了一个解决方案,它是对上面 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);
    }
    

    【讨论】:

      【解决方案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);
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        从 .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 包,则可以使用 prototype ASN.1 writer package(与 .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->binary:

        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 包,则可以使用 prototype ASN.1 reader package(与 .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 Reader/Writer 是否会包含在针对 netstandard 的官方 nuget 中?不在实验性的 myget 提要中。
        • @cleftheris 目前还不是很清楚。您可以在github.com/dotnet/corefx/issues/21833 表达特定的支持矩阵需求。
        猜你喜欢
        • 2013-05-18
        • 1970-01-01
        • 2012-12-12
        • 1970-01-01
        • 1970-01-01
        • 2017-09-20
        • 2017-04-02
        • 2017-04-10
        • 2012-11-19
        相关资源
        最近更新 更多