I am running below code to get public and private key only, but it seems it outputs the whole XML format. I only need to output the keys as shown in 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();
}
Starting in .NET Core 3.0, this is (largely) built-in.
The output of the builtin API is the binary representation, to make them PEM you need to output the header, footer, and 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();
}
So to produce the strings:
string publicKey = MakePem(rsa.ExportSubjectPublicKeyInfo(), "PUBLIC KEY");
string privateKey = MakePem(rsa.ExportRSAPrivateKey(), "RSA PRIVATE KEY");
If you can't use .NET Core 3.0, but you can use pre-release NuGet packages, you can make use of the prototype ASN.1 writer package (which is the same code that's used internally in .NET Core 3.0; it's just that the API surface isn't finalized).
To make the public key:
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");
}
And to make the private 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");
}
Except that .NET Core 3.0 doesn't understand PEM encoding, so you have to do PEM->binary yourself:
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));
}
Once that's done you can now load the keys:
using (RSA rsa = RSA.Create())
{
rsa.ImportRSAPrivateKey(PemToBer(pemPrivateKey, RsaPrivateKey), out _);
...
}
using (RSA rsa = RSA.Create())
{
rsa.ImportSubjectPublicKeyInfo(PemToBer(pemPublicKey, SubjectPublicKeyInfo), out _);
...
}
If you can't use .NET Core 3.0, but you can use pre-release NuGet packages, you can make use of the prototype ASN.1 reader package (which is the same code that's used internally in .NET Core 3.0; it's just that the API surface isn't finalized).
For the public key:
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();
}
And because the private key values have to have the correct size arrays, the private key one is just a little trickier:
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;
}
The Bouncycastle C# library has some helper classes that can make this relatively easy. It is not well documented unfortunately. Here is an example:
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);
}
}
}
I wanted to extract public and private key as char array, and not as string. I found one solution which is a modification of answers provided by James and Vikram above. It could be helpful for someone looking for it.
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);
}
Adding on to the above answer, to get public and private key separately in string format, you can use the following code snippet.
public static void GenerateKeyPair()
{
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
// string publicKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuQHKXMfmDMHNlbRFfOsr\nagIuFKaob4+UrG3WiTdWbreopLQ5falE+8JuO9FaKPehoqbkR+5TcpjsC3etLB+u\n2AjRPdUo/dgXYuqtlSL24Yj6MYpvGry8EUc7MHXsS8ybp12i7U9n5sENBER2dAKE\nyvJZfNpgwN1hNxLZlxUFjRk0+by5q7iiSED9UaGut2M1nVKeoU24M3yRYyJ0Rzia\n3eU0F1pxnyPaHGiIRRCUagErvqGxpKip4tq+7OhBYXTpsya2VKFmMz0vMeXBpIZA\nESdAJiO6u47DG49To2dRJRtw5E54+MzRBAt3rGRtLtAnFY8cnMR3ggxgL5zYURqA\nWwIDAQAB\n-----END PUBLIC KEY-----\n";
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);
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.