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

时间:2019-01-01 18:28:49

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

我正在下面的代码下运行,仅获取公钥和私钥,但似乎它输出了整个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();
        }

2 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:1)

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