Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Import and export RSA Keys between C# and PEM format using BouncyCastle
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Security.Cryptography;
namespace MyProject.Data.Encryption
{
public class RSAKeys
{
/// <summary>
/// Import OpenSSH PEM private key string into MS RSACryptoServiceProvider
/// </summary>
/// <param name="pem"></param>
/// <returns></returns>
public static RSACryptoServiceProvider ImportPrivateKey(string pem) {
PemReader pr = new PemReader(new StringReader(pem));
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();// cspParams);
csp.ImportParameters(rsaParams);
return csp;
}
/// <summary>
/// Import OpenSSH PEM public key string into MS RSACryptoServiceProvider
/// </summary>
/// <param name="pem"></param>
/// <returns></returns>
public static RSACryptoServiceProvider ImportPublicKey(string pem) {
PemReader pr = new PemReader(new StringReader(pem));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey);
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();// cspParams);
csp.ImportParameters(rsaParams);
return csp;
}
/// <summary>
/// Export private (including public) key from MS RSACryptoServiceProvider into OpenSSH PEM string
/// slightly modified from https://stackoverflow.com/a/23739932/2860309
/// </summary>
/// <param name="csp"></param>
/// <returns></returns>
public static string ExportPrivateKey(RSACryptoServiceProvider csp)
{
StringWriter outputStream = new StringWriter();
if (csp.PublicOnly) throw new ArgumentException("CSP does not contain a private key", "csp");
var parameters = csp.ExportParameters(true);
using (var stream = new MemoryStream())
{
var writer = new BinaryWriter(stream);
writer.Write((byte)0x30); // SEQUENCE
using (var innerStream = new MemoryStream())
{
var innerWriter = new BinaryWriter(innerStream);
EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version
EncodeIntegerBigEndian(innerWriter, parameters.Modulus);
EncodeIntegerBigEndian(innerWriter, parameters.Exponent);
EncodeIntegerBigEndian(innerWriter, parameters.D);
EncodeIntegerBigEndian(innerWriter, parameters.P);
EncodeIntegerBigEndian(innerWriter, parameters.Q);
EncodeIntegerBigEndian(innerWriter, parameters.DP);
EncodeIntegerBigEndian(innerWriter, parameters.DQ);
EncodeIntegerBigEndian(innerWriter, parameters.InverseQ);
var length = (int)innerStream.Length;
EncodeLength(writer, length);
writer.Write(innerStream.GetBuffer(), 0, length);
}
var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
// WriteLine terminates with \r\n, we want only \n
outputStream.Write("-----BEGIN RSA PRIVATE KEY-----\n");
// Output as Base64 with lines chopped at 64 characters
for (var i = 0; i < base64.Length; i += 64)
{
outputStream.Write(base64, i, Math.Min(64, base64.Length - i));
outputStream.Write("\n");
}
outputStream.Write("-----END RSA PRIVATE KEY-----");
}
return outputStream.ToString();
}
/// <summary>
/// Export public key from MS RSACryptoServiceProvider into OpenSSH PEM string
/// slightly modified from https://stackoverflow.com/a/28407693
/// </summary>
/// <param name="csp"></param>
/// <returns></returns>
public static string ExportPublicKey(RSACryptoServiceProvider csp)
{
StringWriter outputStream = new StringWriter();
var parameters = csp.ExportParameters(false);
using (var stream = new MemoryStream())
{
var writer = new BinaryWriter(stream);
writer.Write((byte)0x30); // SEQUENCE
using (var innerStream = new MemoryStream())
{
var innerWriter = new BinaryWriter(innerStream);
innerWriter.Write((byte)0x30); // SEQUENCE
EncodeLength(innerWriter, 13);
innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER
var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
EncodeLength(innerWriter, rsaEncryptionOid.Length);
innerWriter.Write(rsaEncryptionOid);
innerWriter.Write((byte)0x05); // NULL
EncodeLength(innerWriter, 0);
innerWriter.Write((byte)0x03); // BIT STRING
using (var bitStringStream = new MemoryStream())
{
var bitStringWriter = new BinaryWriter(bitStringStream);
bitStringWriter.Write((byte)0x00); // # of unused bits
bitStringWriter.Write((byte)0x30); // SEQUENCE
using (var paramsStream = new MemoryStream())
{
var paramsWriter = new BinaryWriter(paramsStream);
EncodeIntegerBigEndian(paramsWriter, parameters.Modulus); // Modulus
EncodeIntegerBigEndian(paramsWriter, parameters.Exponent); // Exponent
var paramsLength = (int)paramsStream.Length;
EncodeLength(bitStringWriter, paramsLength);
bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength);
}
var bitStringLength = (int)bitStringStream.Length;
EncodeLength(innerWriter, bitStringLength);
innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength);
}
var length = (int)innerStream.Length;
EncodeLength(writer, length);
writer.Write(innerStream.GetBuffer(), 0, length);
}
var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
// WriteLine terminates with \r\n, we want only \n
outputStream.Write("-----BEGIN PUBLIC KEY-----\n");
for (var i = 0; i < base64.Length; i += 64)
{
outputStream.Write(base64, i, Math.Min(64, base64.Length - i));
outputStream.Write("\n");
}
outputStream.Write("-----END PUBLIC KEY-----");
}
return outputStream.ToString();
}
/// <summary>
/// https://stackoverflow.com/a/23739932/2860309
/// </summary>
/// <param name="stream"></param>
/// <param name="length"></param>
private static void EncodeLength(BinaryWriter stream, int length)
{
if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
if (length < 0x80)
{
// Short form
stream.Write((byte)length);
}
else
{
// Long form
var temp = length;
var bytesRequired = 0;
while (temp > 0)
{
temp >>= 8;
bytesRequired++;
}
stream.Write((byte)(bytesRequired | 0x80));
for (var i = bytesRequired - 1; i >= 0; i--)
{
stream.Write((byte)(length >> (8 * i) & 0xff));
}
}
}
/// <summary>
/// https://stackoverflow.com/a/23739932/2860309
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
/// <param name="forceUnsigned"></param>
private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
{
stream.Write((byte)0x02); // INTEGER
var prefixZeros = 0;
for (var i = 0; i < value.Length; i++)
{
if (value[i] != 0) break;
prefixZeros++;
}
if (value.Length - prefixZeros == 0)
{
EncodeLength(stream, 1);
stream.Write((byte)0);
}
else
{
if (forceUnsigned && value[prefixZeros] > 0x7f)
{
// Add a prefix zero to force unsigned if the MSB is 1
EncodeLength(stream, value.Length - prefixZeros + 1);
stream.Write((byte)0);
}
else
{
EncodeLength(stream, value.Length - prefixZeros);
}
for (var i = prefixZeros; i < value.Length; i++)
{
stream.Write(value[i]);
}
}
}
public static void runTests(){
Console.WriteLine("Starting RSAKeys tests");
RSACryptoServiceProvider initialProvider = new RSACryptoServiceProvider(2048);
String privateKey = RSAKeys.ExportPrivateKey(initialProvider);
String publicKey = RSAKeys.ExportPublicKey(initialProvider);
Console.WriteLine("-----------------------");
Console.Write("Private Key exported: ");
Console.WriteLine(privateKey);
Console.WriteLine("-----------------------");
Console.Write("Public Key exported: ");
Console.WriteLine(publicKey);
RSACryptoServiceProvider importedProvider = RSAKeys.ImportPrivateKey(privateKey);
privateKey = RSAKeys.ExportPrivateKey(importedProvider);
publicKey = RSAKeys.ExportPublicKey(importedProvider);
Console.WriteLine("-----------------------");
Console.Write("Private Key imported/exported: ");
Console.WriteLine(privateKey);
Console.WriteLine("-----------------------");
Console.Write("Public Key imported/exported: ");
Console.WriteLine(publicKey);
Console.WriteLine("RSAKeys tests completed");
}
}
}
@mgochmuradov

This comment has been minimized.

Copy link

@mgochmuradov mgochmuradov commented May 3, 2018

The name 'DotNetUtilities' does not exist in the current context

Where did you get this?

@trenthaynes

This comment has been minimized.

Copy link

@trenthaynes trenthaynes commented May 7, 2018

That's part of BouncyCastle.

@anand1302

This comment has been minimized.

Copy link

@anand1302 anand1302 commented Oct 8, 2018

The name 'DotNetUtilities' does not exist in the current context , can you please tell me what is library for that in uwp app .

@bitcoinbrisbane

This comment has been minimized.

Copy link

@bitcoinbrisbane bitcoinbrisbane commented Dec 19, 2018

The name 'DotNetUtilities' does not exist in the current context

Where did you get this?

Doesn't seem to be in the Nuget Package version, https://github.com/chrishaly/bc-csharp/search?q=DotNetUtilities&unscoped_q=DotNetUtilities but in the master version https://github.com/bcgit/bc-csharp/blob/f18a2dbbc2c1b4277e24a2e51f09cac02eedf1f5/crypto/src/security/DotNetUtilities.cs

@soondook

This comment has been minimized.

Copy link

@soondook soondook commented Nov 9, 2019

Method "RSACryptoServiceProvider ImportPublicKey", Doesn't work
It will be easier, and this way and this option works.
public static RSACryptoServiceProvider ImportPublicKey(string pub) { PemReader pr2 = new PemReader(new StringReader(pub)); AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr2.ReadObject(); RSAParameters rsaParam = DotNetUtilities.ToRSAParameters((RsaKeyParameters)KeyPair.Public); RSACryptoServiceProvider csp = new RSACryptoServiceProvider();// cspParams); csp.ImportParameters(rsaParam); Console.WriteLine(csp.ToXmlString(**false**)); return csp; }

@therightstuff

This comment has been minimized.

Copy link
Owner Author

@therightstuff therightstuff commented Nov 9, 2019

Method "RSACryptoServiceProvider ImportPublicKey", Doesn't work
It will be easier, and this way and this option works.
public static RSACryptoServiceProvider ImportPublicKey(string pub) { PemReader pr2 = new PemReader(new StringReader(pub)); AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr2.ReadObject(); RSAParameters rsaParam = DotNetUtilities.ToRSAParameters((RsaKeyParameters)KeyPair.Public); RSACryptoServiceProvider csp = new RSACryptoServiceProvider();// cspParams); csp.ImportParameters(rsaParam); Console.WriteLine(csp.ToXmlString(**false**)); return csp; }

I've just added a couple of tests (added to this gist) and run it on VSCode on a Mac using the latest BouncyCastle version from NuGet, works perfectly for me. Could you please provide more context? Also, your code block doesn't let me know which packages you're including, it would be great if you created a gist with the complete class file and posted a link here.

If there's an easier way to do this, I'm all for it!

@soondook

This comment has been minimized.

Copy link

@soondook soondook commented Nov 9, 2019

I apologize for my English:))
In my project, I use the following components:
Microsoft Visual Studio Community 2019 / VisualStudio.16.Release/16.2.5+29306.81
Microsoft.CSharp: v4.0.30319
BouncyCastle.Crypto: 1.8.1.0 / v1.1.4322 (latest version from NuGet)
When I try use your codeblock, this leads to an unhandled exception:
System.InvalidCastException: "Failed to cast object type "Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair" to type "Org.BouncyCastle.Crypto.AsymmetricKeyParameter"."

Thank you for the good job! Hope our efforts help someone else.. ))
Best Regards!

@therightstuff

This comment has been minimized.

Copy link
Owner Author

@therightstuff therightstuff commented Nov 11, 2019

Your English is just fine :)

The error you're receiving is because you're providing the ImportPrivateKey method a public key, and it must receive a private key. If you are trying to import a public key, you need to use ImportPublicKey.

@sebagomez

This comment has been minimized.

Copy link

@sebagomez sebagomez commented Jan 27, 2020

you sir, are a lifesaver

@therightstuff

This comment has been minimized.

Copy link
Owner Author

@therightstuff therightstuff commented Jan 27, 2020

you sir, are a lifesaver

Glad to be able to help!

@ridhoq

This comment has been minimized.

Copy link

@ridhoq ridhoq commented Feb 26, 2020

FYI, for anyone who is stuck by not finding the DotNetUtilities: see this comment: bcgit/bc-csharp#113 (comment). You are probably using the wrong NuGet package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.