.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)
制作私钥:
private static string ToRSAPrivateKey(RSA rsa)
读取它们
.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)
byte[] modulus = ReadNormalizedInteger(rsaPrivateKey);
int halfModulusLen = (modulus.Length + 1) / 2;
RSAParameters rsaParameters = new RSAParameters
;
rsaPrivateKey.ThrowIfNotEmpty();
RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParameters);
return rsa;
}
private static byte[] ReadNormalizedInteger(AsnReader reader, int length)
byte[] buf = new byte[length];
int skipSize = length - span.Length;
span.CopyTo(buf.AsSpan(skipSize));
return buf;
}