I have a random private key ("C:\tmp\private.key"):
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDHikastc8+I81zCg/qWW8dMr8mqvXQ3qbPAmu0RjxoZVI47tvs
kYlFAXOf0sPrhO2nUuooJngnHV0639iTTEYG1vckNaW2R6U5QTdQ5Rq5u+uV3pMk
7w7Vs4n3urQ6jnqt2rTXbC1DNa/PFeAZatbf7ffBBy0IGO0zc128IshYcwIDAQAB
AoGBALTNl2JxTvq4SDW/3VH0fZkQXWH1MM10oeMbB2qO5beWb11FGaOO77nGKfWc
bYgfp5Ogrql4yhBvLAXnxH8bcqqwORtFhlyV68U1y4R+8WxDNh0aevxH8hRS/1X5
031DJm1JlU0E+vStiktN0tC3ebH5hE+1OxbIHSZ+WOWLYX7JAkEA5uigRgKp8ScG
auUijvdOLZIhHWq7y5Wz+nOHUuDw8P7wOTKU34QJAoWEe771p9Pf/GTA/kr0BQnP
QvWUDxGzJwJBAN05C6krwPeryFKrKtjOGJIniIoY72wRnoNcdEEs3HDRhf48YWFo
riRbZylzzzNFy/gmzT6XJQTfktGqq+FZD9UCQGIJaGrxHJgfmpDuAhMzGsUsYtTr
iRox0D1Iqa7dhE693t5aBG010OF6MLqdZA1CXrn5SRtuVVaCSLZEL/2J5UcCQQDA
d3MXucNnN4NPuS/L9HMYJWD7lPoosaORcgyK77bSSNgk+u9WSjbH1uYIAIPSffUZ
bti+jc1dUg5wb+aeZlgJAkEAurrpmpqj5vg087ZngKfFGR5rozDiTsK5DceTV97K
a3Y+Nzl+XWTxDBWk4YPh2ZlKv402hZEfWBYxUDn5ZkH/bw==
-----END RSA PRIVATE KEY-----
I tried to use the RSACryptoServiceProvider.ImportCspBlob to import it but it failed with the error:
System.Security.Cryptography.CryptographicException: Bad Version of
provider.
Full stack trace:
Failed: System.Security.Cryptography.CryptographicException: Bad Version of provider.
at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
at System.Security.Cryptography.Utils._ImportCspBlob(Byte[] keyBlob, SafeProvHandle hProv, CspProviderFlags flags, SafeKeyHandle& hKey)
at System.Security.Cryptography.Utils.ImportCspBlobHelper(CspAlgorithmType keyType, Byte[] keyBlob, Boolean publicOnly, CspParameters& parameters, Boolean randomKeyContainer, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.ImportCspBlob(Byte[] keyBlob)
at ConsoleApplication3.Program.DecodeRSA(Byte[] data, Int32 c_data) in C:\Users\myuser\Documents\Visual Studio 2015\Projects\myproj\ConsoleApplication3\Program.cs:line 28
at ConsoleApplication3.Program.Main(String[] args) in C:\Users\myuser\Documents\Visual Studio 2015\Projects\myproj\ConsoleApplication3\Program.cs:line 14
Press any key to continue . . .
Any idea what I am doing wrong?
This is my code:
using System;
using System.Security.Cryptography;
namespace ConsoleApplication3
{
class Program
{
static public byte[] privateKey;
static void Main(string[] args)
{
try
{
privateKey = System.IO.File.ReadAllBytes(#"C:\tmp\private.key");
DecodeRSA(privateKey);
}
catch(Exception e)
{
Console.WriteLine("Failed: {0}", e);
}
}
static public void DecodeRSA(byte[] data)
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportCspBlob(Program.privateKey);
}
}
}
}
Your private key has a PKCS1-PEM-Format. Private key BLOBs have another format. As already mentioned in the comments, the formats are very different and cannot be easily converted (see e.g. here). Of course you can use a PKCS1-PEM-key, but it is not that easy. Here are some options:
Possibility 1:
If you use .NET Core 3.0 there is a direct support for reading a PKCS1-key (see also here):
byte[] privateKeyPkcs1PEM = File.ReadAllBytes(#"C:\tmp\private.key"); // PKCS1 - PEM
byte[] privateKeyPkcs1DER = ConvertPKCS1PemToDer(Encoding.UTF8.GetString(privateKeyPkcs1PEM));
RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(privateKeyPkcs1DER, out _);
// use e.g. rsa.Decrypt(...)
However, the ImportRSAPrivateKey-method can only process the DER-format which is essentially the binary format of the PEM-format (for more details see here or here). Thus, you have to convert the PEM-format into the DER-format with something like
private static byte[] ConvertPKCS1PemToDer(string pemContents)
{
return Convert.FromBase64String(pemContents
.TrimStart("-----BEGIN RSA PRIVATE KEY-----".ToCharArray())
.TrimEnd("-----END RSA PRIVATE KEY-----".ToCharArray())
.Replace("\r\n",""));
}
Alternatively, OpenSSL can also be used for the conversion:
openssl rsa -inform PEM -outform DER -in C:\tmp\private.key -out C:\tmp\private.der
Possibility 2:
You can convert your PKCS1-PEM-key into an PKCS8-DER-key using OpenSSL. The appropriate command is:
openssl pkcs8 -topk8 -inform pem -in C:\tmp\private.key -outform der -nocrypt -out C:\tmp\privatepkcs8.der
The difference between PKCS1- and PKCS8-format is explained here. Then you can import the key with built-in .NET-methods (see also here, section PKCS#8 PrivateKeyInfo):
byte[] privateKeyPkcs8DER = File.ReadAllBytes(#"C:\tmp\privatepkcs8.der"); // PKCS8 - DER
CngKey cngKey = CngKey.Import(privateKeyPkcs8DER, CngKeyBlobFormat.Pkcs8PrivateBlob);
RSA rsa = new RSACng(cngKey);
// use e.g. rsa.Decrypt(...)
Possibility 3:
If third-party-libraries may be used, BouncyCastle is also a possibility:
StreamReader reader = File.OpenText(#"C:\tmp\private.key"); // PKCS1 - PEM
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
Pkcs1Encoding pkcs1Encoding = new Pkcs1Encoding(new RsaEngine());
// E.g. decryption
pkcs1Encoding.Init(false, keyPair.Private);
byte[] decrypted = pkcs1Encoding.ProcessBlock(encrypted, 0, encrypted.Length);
Possibility 4:
Another possibility is the use of the DecodeRSAPrivateKey-method from JavaScience which can process a PKCS1-key. However, the DecodeRSAPrivateKey-method can only process the DER-format. Thus, you must first manually convert the PEM-format into the DER-format using e.g. ConvertPKCS1PemToDer or OpenSSL.
Thanks to the #Topaco answer, I understood that I need to find the CSP blob and like he mentioned, it is undocumented.
So I just used byte[] a = rsa.ExportCspBlob(true); to get it.
I converted the bytes to base64 and this is a correct key format in base64 that worked for me:
BwIAAACkAABSU0EyAAQAAAEAAQD99dvdVctFcYP6fGCvz/8QcoJqjpfKMPxCIsVRAZSCaKTB6Dl0DbEQBcaLNe8Cm31lzMYyf/2vh6gM+GUHmKcBYo2Z7JvauTGXFXEyv02ai8RINlvAGAicZwWoyGJb5h4sM881Q5+BuDTcoyefk+b7x7KBQjMD/wNuPCWijZ0lsP+Gt1tPryE757QDDl95jQk04ZS+70vGOAO836f+RCyeA6c0ZEA1eYzsM/PVsv+nLwh7pTj7KLFSha5CM304SdcDnyOnt1ARyv1BQsRhyN3IAOH/Se00OfWhcc0sZCjg+xvDebKuoODHDhUfHJPchOmyvhSxjyNACJuxg1uGh3XRmaPoceXXFCuNhFGheK5cQrfUGHpWeJKrpWM/+f3XcrYob0jQCloBIicXfvhhPnkPojiOquxmjy0rA8/JRjHov3+znJY+pQgFC5cUmvGWxhWygm+qDwYco6yCSRkkaIp/K39uJXQ2pQf9XapqjtAJipRo5xX0o/itiDyF1qPT7TumZROMUhU3znXGnxPelZ2bA7SgPiu6BBKADfqG1XJE1K50ydaEfyXYceYHIs7UAMLw9aTptqHbPPGp1hDL2xpWBR6hvqkPqouiVJ7VgPHkjxwT/hgXBvJbHOm/ghq/xA/1oTtXLJHXCASVdylt+nwauOp5qR0Dfdbz7IQGjChYzBHuqDuKorpmfHhZl+bDTHpJ1PjWrojoBfAt2v5zlBnw/ipjkD9MXKrNlPqbgeYXUAeAzfFKQhF2kr3zlmExIS8=
It is important to mentioned that I just need that this function will pass successfully because I am working on a blackbox application and I can't change the code, I need to provide it with the right input.
Related
I have a private key that was generated by running:
openssl req -new -sha384 -x509 -days 63524 -subj "/C=CA/ST=State/L=City/O=Org/CN=Org-CA" -extensions v3_ca -keyout Org-CA.key -out Org-CA.pem -passout pass:pass1234
I need to use this private key and certificate to sign client CSRs. I used to use OpenSSL to do this, but I need to do this in C# instead.
I've found examples of what I want to do online, but those examples are using bouncy castle and an un-encrypted private key.
The key file looks like this:
-----BEGIN ENCRYPTED PRIVATE KEY-----
....
-----END ENCRYPTED PRIVATE KEY-----
And I'm trying to load it by doing this:
var privatekey = File.ReadAllBytes(#"Org-CA.key");
var rsaKeyParameters = PrivateKeyFactory.DecryptKey("pass1234".ToArray(), privatekey);
But thats not working. I get an error saying Wrong number of elements in sequence (Parameter 'seq')'
Is this how I'm supposed to be decrypting and loading the private key?
Depending on your .NET version, you may not need BouncyCastle at all. As of .NET Core 3.1 there is RSA.ImportEncryptedPkcs8PrivateKey() for DER encoded encrypted private PKCS#8 keys and as of .NET 5.0 there is even RSA.ImportFromEncryptedPem() for PEM encoded encrypted keys.
Otherwise with C#/BouncyCastle the import of an encrypted private PKCS#8 key is available e.g. with:
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
...
string encPkcs8 = #"-----BEGIN ENCRYPTED PRIVATE KEY-----
...
-----END ENCRYPTED PRIVATE KEY-----";
StringReader stringReader = new StringReader(encPkcs8);
PemReader pemReader = new PemReader(stringReader, new PasswordFinder("<your password>"));
RsaPrivateCrtKeyParameters keyParams = (RsaPrivateCrtKeyParameters)pemReader.ReadObject();
...
with the following implementation of the IPasswordFinder interface:
private class PasswordFinder : IPasswordFinder
{
private string password;
public PasswordFinder(string pwd) => password = pwd;
public char[] GetPassword() => password.ToCharArray();
}
If necessary, a conversion from keyParams to System.Security.Cryptography.RSAParameters is possible with:
using Org.BouncyCastle.Security;
...
RSAParameters rsaParameters = DotNetUtilities.ToRSAParameters(keyParams);
which can be imported directly e.g. with RSA.ImportParameters().
Edit:
After digging through the C#/BouncyCastle source code, your way is also possible (which I didn't know before).
DecryptKey() has several overloads, one of which can handle the DER encoded encrypted key as already suspected by Maarten Bodewes in his comment. The latter can be easily generated with the Org.BouncyCastle.Utilities.IO.Pem.PemReader() class.
Note that this PemReader is different from the one in the first implementation (different namespaces), which is why I use the namespace explicitly in the following code snippet. With this approach, the RsaPrivateCrtKeyParameters instance can be generated as follows:
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Parameters;
...
StringReader stringReader = new StringReader(encPkcs8);
Org.BouncyCastle.Utilities.IO.Pem.PemReader pemReader = new Org.BouncyCastle.Utilities.IO.Pem.PemReader(stringReader);
Org.BouncyCastle.Utilities.IO.Pem.PemObject pem = pemReader.ReadPemObject();
RsaPrivateCrtKeyParameters keyParams = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.DecryptKey("<your password>".ToCharArray(), pem.Content);
...
pem.Content contains the DER encoded encrypted private PKCS#8 key.
I generated an RSA Public Key in C# with RSACryptoServiceProvider with a key size of 512-bits.
// Generate a public/private key using RSA
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(512);
// Read public key in a string
string str = RSA.ToXmlString(true);
Console.WriteLine(str);
// XML output
<RSAKeyValue>
<Modulus>26+2lMVkqh99O3nBV/EU5KkO3FYx7R/0Fnlh6FES2wvIAZ6yaHrAhrX6a/BWag4N/XRH3z+nxQBRiG1pPUDQKQ==</Modulus>
<Exponent>AQAB</Exponent>
<P>9y6RKpt6OsQFoPfsxo9+NpSJF2xrWg9+h+f6ri7svv8=</P>
<Q>44YJ0p9qDJuvIM+PaFi9p8NpYerSUAfO49camwH7mNc=</Q>
<DP>Zw9ebXJn8yqZ4jSc32kiyaUCx+ZnmCRPsGCzr35XLYc=</DP>
<DQ>Gn4OAL9dKtCp6KkiaqUCmFkxmRwtlvIBzhfK2ke10ws=</DQ>
<InverseQ>6bwnrFXgBggw2+9fyaPP4femrMH3VQgxBkj8P1B3TTs=</InverseQ>
<D>SJpisfomkZ7EiZJsln7DU+qXUbRe3aowxfippdidbaxzM3+lMgLH2V8q9Ba7M8leG8mxkvttdilojclFFi9q3Q==</D>
</RSAKeyValue>
I throw the Modulus value into a PEM file:
-----BEGIN PUBLIC KEY-----
26+2lMVkqh99O3nBV/EU5KkO3FYx7R/0Fnlh6FES2wvIAZ6yaHrAhrX6a/BWag4N/XRH3z+nxQBRiG1pPUDQKQ==
-----END PUBLIC KEY-----
OpenSSL is not able to decode the Public Key, however. Here is the command I used:
openssl rsa -pubin -inform PEM -text -noout < public-key.pem
And here is the result with errors:
unable to load Public Key
30844:error:0D07207B:asn1 encoding routines:ASN1_get_object:header too long:crypto\asn1\asn1_lib.c:101:
30844:error:0D068066:asn1 encoding routines:asn1_check_tlen:bad object header:crypto\asn1\tasn_dec.c:1137:
30844:error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error:crypto\asn1\tasn_dec.c:309:Type=X509_PUBKEY
30844:error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib:crypto\pem\pem_oth.c:33:
Why can't OpenSSL decode this Public Key? I even tried the default key size of 1024-bits and it still couldn't read it. Is there a compatibility issue between OpenSSL and the RSACryptoServiceProvider class? How can I get OpenSSL to properly read and decode this Public Key?
I'm trying to decode the HelloWorks callback signature to add security to an endpoint. As it stays in the documentation I need to generate a private key using the following openssl command:
openssl genrsa -des3 -out helloworks.pem 4096
and then make the public key:
openssl rsa -in helloworks.pem -outform PEM -pubout -out public.pem
I have created both keys and configured it. Now I need to decode base64 the X-Helloworks-Signature sent by them, then decrypt the result using the private key.
I have been trying several ways to do this in C# but with no luck. One approach that I have done using BouncyCastle library is:
var signature = mvcContext.HttpContext.Request.Headers["X-Helloworks-Signature"];
var url = mvcContext.HttpContext.Request.Path.Value;
var bytesToDecrypt = Convert.FromBase64String(signature);
AsymmetricCipherKeyPair keyPair;
var pemPath = Path.Combine(env.ContentRootPath, "./helloworks.pem");
using (var reader = File.OpenText(pemPath))
keyPair = (AsymmetricCipherKeyPair)new PemReader(reader, new PasswordFinder("password")).ReadObject();
var decryptEngine = new Pkcs1Encoding(new RsaEngine());
decryptEngine.Init(false, keyPair.Private);
var decryptedToken = Encoding.UTF8.GetString(decryptEngine.ProcessBlock(bytesToDecrypt, 0, bytesToDecrypt.Length));
But it always throws the same exception message:
Org.BouncyCastle.Crypto.DataLengthException: 'input too large for RSA cipher.'
I have been trying other approaches that I have found here in SO and the web but no luck.
How can I use the private key pem file to decrypt the signature in C#, specifically .Net Core 2?
Update 1
An example of the signature is:
X-Helloworks-Signature: bGNTNUVOZ0w5akx4U1VXUTFrcHQ0ZmZSZU1KQnNpbDhwVDFGVllSMzF5aVdtQkZOcTRMTFNrY2FmV3o5aFZJVk5rbmVRUEdydTZLZ1BHQ1dCdkFYVVRMWUs1bUYxSXplam5mZVEwVFZvMjFZRS1rM0l4SDBkNU45clJDX1RYOVZPcThwUzI5V2pTd1k2d0kxaGQ0VXpiSkU2NERJaVljaWxCWkJwVVJhN0NRcGphbkxlZV8tZFMxQWtUbTdRUHdEVTZtVDNnc2ZPQkNiMFZMeUR2TGVtdmVTZldXZnVUOXg0NTRYSU1rZjE3elRvR0xHT29YeEpKWjk5LUcyc3VjWkk5NWpmRldvaGo0S2Z0dXV1SHMtMmpZajZuNXhpMFI5S0hmT2xNRkRwRlFtbEpUbG9TTURGREVPeDE3RjNoX1NXMk1RSUR2b2hGazBtTkstMk9OVHhHQ05tQXhuS1c5Nm9SN0N4QlJnQ2xyLXlvOXJTWXp2anNlVVhGZ0hqakYzZE8ybGhUdXFPV1A0NU9TaUVoY21zSHVMRnV0Zy10a0J5QTJLYTB2Yk1sNi1wMVlLR09QZVdMVm9QN0k5ellyWFdNRFl0S2dPaXZkSldEa1B0SmdvbVMwbU1DdWMyblBFUHFxUTEtQkdJMHNFdjNNWVFfYVNjeGJsZ2p2VHVZZzRmZy1VXzk3R0pON1ZsM1IwWllZSF9QU2syQll6LTN3Vjc3QjMxbGFjT3lWSU15WDVJdktKWXIwTXZMQ190cjIwZk9hZkx5c011eWpsSXV6Tl96Q2VHbkpfbkJiQnJYNXNyNmktZDB4UW9rc0JKSUtUZUNMR3dVWWEtVEJVUE5FeWplM09RT0NOYW02R242OEdLeDRZdTlzZDVNa1BCQ1B6NTI3aUhoYm9aLTg9
Thanks to James K Polk comment I ended doing the following method:
private static byte[] Base64Decode(StringValues signature)
{
string incoming = signature.ToString().Replace('_', '/').Replace('-', '+');
switch (signature.ToString().Length % 4)
{
case 2:
incoming += "==";
break;
case 3:
incoming += "=";
break;
}
return Convert.FromBase64String(incoming);
}
and modifying my code from:
var bytesToDecrypt = Convert.FromBase64String(signature);
to:
var decoded = Base64Decode(signature);
var text = Encoding.ASCII.GetString(decoded);
var bytesToDecrypt = Base64Decode(text);
I needed to decode two times the signature using the URL-safe version of base64 decoding.
I am trying to create an online database application using PHP for the server and C# form application for the client.
On the server I encrypt a simple string using a public RSA key with the PHPSecLib. Then the C# application receives the string and tries to decrypt it using the corresponding private key.
The bytes are base64 encoded on the server and decoded to bytes again by C#. I created the key pair using the PHPSecLib.
This is the code I use on the client application:
public string rsa_decrypt(string encryptedText, string privateKey) {
byte[] bytesToDecrypt = Convert.FromBase64String(encryptedText);
Pkcs1Encoding decrypter = new Pkcs1Encoding(new RsaEngine());
//the error occurs on this line:
AsymmetricCipherKeyPair RSAParams = (AsymmetricCipherKeyPair)new PemReader(new StringReader(privateKey)).ReadObject();
decrypter.Init(false, RSAParams.Private);
byte[] decryptedBytes = decrypter.ProcessBlock(bytesToDecrypt, 0, bytesToDecrypt.Length);
string decryptedString = Convert.ToBase64String(decryptedBytes);
return decryptedString;
}
But, I get the following error on the line specified above^.
An unhandled exception of type 'System.IO.IOException' occurred in
BouncyCastle.Crypto.dll
Additional information: -----END RSA PRIVATE KEY not found
I believe there's nothing wrong with the key pair combo as I get an error before I even try to decrypt anything.
The privateKey parameter is currently hardcoded into the script using this format:
string privateKey = "-----BEGIN RSA PRIVATE KEY-----XXXXXXXX-----END RSA PRIVATE KEY-----";
So it seems to me the footer actually is included in the string... I have debugged and googled everywhere but I can't seem to solve it. I'm pretty new to RSA&Bouncycastle so maybe I'm just using wrong methods.
Hope you can help, thanks!
- G4A
P.S. This is my first Stackoverflow question, I just created an account, so if you could also give me some feedback on the way I formulated this question; great!
You need to add a new line between the pre/post encapsulation boundary text and the Base64 data, so:
string privateKey = "-----BEGIN RSA PRIVATE KEY-----\r\nXXX\r\n-----END RSA PRIVATE KEY-----";
This is because the pem specification allows for the existence of other textual headers between the two.
If this doesn't work
"-----BEGIN RSA PRIVATE KEY-----\r\nXXXXXXXX\r\n-----END RSA PRIVATE KEY-----"
please try this
"-----BEGIN RSA PRIVATE KEY-----
XXXXXXXX
-----END RSA PRIVATE KEY-----"
We converted the BOX Private Key to Base64 Format and stored the same in Azure Vault.
Convert key to Base64 using Base64Encode method, store in Azure Key Vault.
Retrieve the encoded string in code, decoded back using Base64Decode Method.
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
public static string Base64Decode(string base64EncodedData)
{
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
I recommend use \x0A instead of \r\n and
.
Because only this option worked for me.
So :
"-----BEGIN RSA PRIVATE KEY-----\x0AXXXXXXXX\x0A-----END RSA PRIVATE KEY-----"
I only have a private key in a pem file and need to extract the public key from it.
At the moment I'm using openssl for this:
openssl rsa -pubout -outform DER
Ho can I do the same in c# (and Bouncycastle)?
In windows world file that contains private key is called PFX.
So you can try to import certificate to collection using .net System.Security.Cryptography infrastructure:
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(filePath, password, X509KeyStorageFlags.PersistKeySet);
The code block was taken from here:
How to retrieve certificates from a pfx file with c#?
If your .pem file already contains the public key it is usually between the Lines
-----BEGIN PUBLIC KEY-----
and
-----END PUBLIC KEY-----
of the pem file.
At least Openssl does it like this, but you should check it carefully.
See this link for an overview over the structure of a pem file.
So you could get it by using the Substring method. This should work:
static void ExtractPublicKeyFromPem(string path)
{
var startText = "-----BEGIN PUBLIC KEY-----";
var endText = "-----END PUBLIC KEY-----";
var pem = System.IO.File.ReadAllText(path);
var start = pem.LastIndexOf(startText);
var end = pem.IndexOf(endText);
var publicKey = pem.Substring(start, end - start);
}
Hope it helps :)