I'm starting to cryptography.
I need to generate a PKCS # 7 file with XML, an RSA private key (which is not included in the certificate is a file extension .key) and a certificate .cer extension.
For this i'm using a BouncyCastle.
Edit:
Thanks #khlr for your help but I can not solve my problem. When sending the data to the AC returns me "Invalid CMS".
I have this code:
public static byte[] FirmaBytesMensaje(byte[] argBytesMsg, X509Certificate2 argCertFirmante)
{
try
{
//Add message in object ContentInfo
ContentInfo infoContenido = new ContentInfo(argBytesMsg);
SignedCms cmsFirmado = new SignedCms(infoContenido);
CmsSigner cmsFirmante = new CmsSigner(argCertFirmante);
cmsFirmante.IncludeOption = X509IncludeOption.EndCertOnly;
// Sign message PKCS #7
cmsFirmado.ComputeSignature(cmsFirmante);
// Encodeo el mensaje PKCS #7.
return cmsFirmado.Encode();
}
catch (Exception excepcionAlFirmar)
{
throw new Exception("***Error: " + excepcionAlFirmar.Message);
}
}
Signing on PKCS # 7 but this makes with a "PFX" certificate, that is, that contains the private key in a ".pfx" file.
When I use the OpenSSL command:
openssl smime -sign -signer cert.crt -inkey private.key -out file.xml.cms -in file.xml -outform PEM -nodetach
The AC responds well.
How I can do this with BouncyCastle and cer and key files? I am going crazy! :-(
Unfortunately there seems to be no bouncycastle API documentation for C#. Never the less there's a Java reference which is said to be quite similar to the C# API.
Hence the getEncoded()-method (look for a C# equivalent, e.g. GetEncoded()) yields a ASN.1 encoded byte[].
You could then go ahead and get a string from that (note that I'm not familiar with ASN.1 encoding. This is just a guess ๐):
byte[] buffer = datosFirmados.GetEncoded();
string signedDataString = System.Text.Encoding.UTF8.GetString(buffer, 0, buffer.Length);
Edit:
Maybe the AsnEncodedData-class would be more appropriate for that task:
byte[] buffer = datosFirmados.GetEncoded();
var asndata = new AsnEncodedData(buffer);
const bool multiline = true;
string signedDataString = asndata.Format(multiline);
It's been a quite time, but still no answer.
You will need to merge cert and key file together as below.
using (System.Security.Cryptography.X509Certificates.X509Certificate2 _pub = this.PublicKey.X509Certificate2)
{
using (X509Certificate2 _pri = _pub.CopyWithPrivateKey(_rsa))
{
var _infoContenido = new System.Security.Cryptography.Pkcs.ContentInfo(Message);
SignedCms _signedCms = new SignedCms(_infoContenido);
CmsSigner _cmsSigner = new CmsSigner(_pri);
if (IncludeSignDate)
{
_cmsSigner.SignedAttributes.Add(new Pkcs9SigningTime(DateTime.Now)); // [2020-05-02] ์๋ช
ํ ๋ ์ง ์์ฑ ์ถ๊ฐ
}
_cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;
// Sign message PKCS #7
_signedCms.ComputeSignature(_cmsSigner);
var _signedMessage = _signedCms.Encode();
}
}
Related
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.
I'm currently attempting to build a system which encrypts customer data and adds it to a remote MSMQ queue which is on another server. The data is then picked up by a job that runs every X Minute which will attempt to decrypt the data and processes it.
There is a requirement of us having to use .PFX Certificate to do the encryption/decryption (which I am aware is not be most efficient way of doing things but the requirement is there and I am unable to get this changed).
I am currently using a self-signed certificate using Open-SSL using:
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
openssl pkcs12 -inkey key.pem -in certificate.pem -export -out certificate.p12
I have been successful at encrypting the data but every time I attempt a decrypt, I get the generic "The parameter is incorrect" exception.
To load the certificate, we have the .PFX file saved locally on the machines and using the X509Certificate2 class, we import it and use it to encrypt and decrypt. Here is a simplified version of the helper class that I am working with:
public static string EncryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.PublicKey.Key as RSACryptoServiceProvider)
{
var dataBytes = Convert.FromBase64String(data);
var encryptedBytes = rsa.Encrypt(dataBytes, false);
return Convert.ToBase64String(encryptedBytes);
}
}
public static string DecryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.PrivateKey as RSACryptoServiceProvider)
{
var dataBytes = Convert.FromBase64String(data);
var decryptedBytes = rsa.Decrypt(dataBytes, false);
return Convert.ToBase64String(decryptedBytes);
}
}
private static X509Certificate2 GetCertificate()
{
var certificate = new X509Certificate2();
certificate.Import("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
return certificate;
}
The error always occurs on the "rsa.Decrypt()" call.
I have attempted the following:
Call "rsa.Decrypt()" in my "EncryptData" method right after encryption. This works with no issue and the "rsa.Decrypt" gives the me same bytes as the original data bytes.
Call "DecryptData" method straight after "EncryptData" call. The same issue occurs and I get Exception with "The parameter is incorrect"
This is why I suspect the fact a "new" X509Certificate2 is created that the private key is no longer the same and can no longer decrypt the data.
Please note that I am no security expert and have not worked with X509 Certificates or any cryptography for that matter so I am a bit out of my depth and may be doing something really silly so please let me know if I am.
Update 1 (08/03/2019)
Have updated the code code as per recommendation points 1-3 & 5 given by #bartonjs
public static string EncryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.GetRSAPublicKey())
{
var dataBytes = Convert.FromBase64String(data);
var encryptedBytes = rsa.Encrypt(dataBytes, RSAEncryptionPadding.OaepSHA1);
return Convert.ToBase64String(encryptedBytes);
}
}
public static string DecryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.GetRSAPrivateKey())
{
var dataBytes = Convert.FromBase64String(data);
var decryptedBytes = rsa.Decrypt(dataBytes, RSAEncryptionPadding.OaepSHA1);
return Convert.ToBase64String(decryptedBytes);
}
}
private static X509Certificate2 GetCertificate()
{
var certificate = new X509Certificate2("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
return certificate;
}
Added error message:
Message: The parameter is incorrect.
Stack Trace:
at System.Security.Cryptography.NCryptNative.DecryptData[T](SafeNCryptKeyHandle key, Byte[] data, T& paddingInfo, AsymmetricPaddingMode paddingMode, NCryptDecryptor`1 decryptor)
at System.Security.Cryptography.NCryptNative.DecryptDataOaep(SafeNCryptKeyHandle key, Byte[] data, String hashAlgorithm)
at System.Security.Cryptography.RSACng.Decrypt(Byte[] data, RSAEncryptionPadding padding)
I'm sorry to say that pretty much everything here is wrong.
GetCertificate
private static X509Certificate2 GetCertificate()
{
var certificate = new X509Certificate2();
certificate.Import("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
return certificate;
}
You're importing with PersistKeySet, so you're slowly filling up your hard drive. See What is the rationale for all the different X509KeyStorageFlags?
Also, you're using certificate.Import, which is not available in .NET Core (because mutating X509Certificate2 objects is unexpected). Just use the constructor. So this whole method should be
private static X509Certificate2 GetCertificate()
{
// Assuming you do nothing else with the certificate than what's shown here,
// EphemeralKeySet will work for you (except on macOS).
return new X509Certificate2(path, password, X509KeyStorageFlags.EphemeralKeySet);
}
EncryptData
public static string EncryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.PublicKey.Key as RSACryptoServiceProvider)
{
var dataBytes = Convert.FromBase64String(data);
var encryptedBytes = rsa.Encrypt(dataBytes, false);
return Convert.ToBase64String(encryptedBytes);
}
}
There are a couple of things wrong here.
1) PrivateKey is a shared property, so if it got read more than once you'd be disposing the object out from under another caller.
2) You're not disposing it if you happened to get a non-RSA certificate
3) You're using PrivateKey, which does not support the better RSA or DSA classes that support modern options.
4) You're the sole handler of the certificate, but didn't dispose it. Maybe your ownership semantics could be more clear.
From a security perspective, also 5) you're using PKCS#1 padding instead of OAEP
From a data perspective, also 6) why is encrypt being given base64 data instead of the raw data?
I won't address #s 4-6.
public static string EncryptData(string data)
{
var certificate = GetCertificate();
using (RSA rsa = certificate.GetRSAPublicKey())
{
var dataBytes = Convert.FromBase64String(data);
var encryptedBytes = rsa.Encrypt(dataBytes, RSAEncryptionPadding.Pkcs1);
return Convert.ToBase64String(encryptedBytes);
}
}
In this case it's correct to put it the private key in a using statement, the GetRSAPrivateKey() method always returns a new object.
DecryptData
Decrypt should be altered similarly to Encrypt.
If, after all of that, you're still getting exceptions, please include the exact message and the stack trace (at least the portions from the call to RSA.Decrypt through where it was thrown)
This is a bit related to the answer to X509Certificate2 from store with private key.
It seems that when I want to use SHA256withRSA I can't use service provider directly from the certificate's PrivateKey - I need to create new crypto service provider:
var bytes = new byte[] { 0, 1, 2, 3 };
//_cert - X509Certificate2 with private key
//csp1 is of type I need, but it won't work
var csp1 = _cert.PrivateKey as RSACryptoServiceProvider;
var cspParameters = new CspParameters
{
KeyContainerName = csp1.CspKeyContainerInfo.KeyContainerName,
KeyNumber = csp1.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2,
};
var csp2 = new RSACryptoServiceProvider(cspParameters);
//I can't use csp1 here - will throw "CryptographicException : Invalid algorithm specified."
//I can use csp1 with "SHA1" though
var signature = csp2.SignData(bytes, CryptoConfig.MapNameToOID("SHA256"));
I've found some information about this here:
https://blogs.msdn.microsoft.com/shawnfa/2008/08/25/using-rsacryptoserviceprovider-for-rsa-sha256-signatures/
But the above solution is taken from comments section and I don't really understand why I need to jump through hoops to use one of the common algorithms. So what I want to ask is:
Why csp1 does not work with SHA256 exactly?
Is it correct to create the csp2 like I did?
Is there a better/newer way I can do it in .NET?
If needed the cert with private key can be generated as follows:
openssl req -x509 -sha256 -newkey rsa:2048 -keyout ./temp/key.key -out ./temp/crt.crt -days 10 โnodes
openssl pkcs12 -export -out .\temp\cert.pfx -inkey .\temp\key.key โin .\temp\crt.crt
It all depends on where your certificate is coming from. Like the MSDN comment says, if it's coming from Microsoft Base Cryptographic Provider, then it will not work with SHA256. This CSP came out with the first version of CryptoAPI back in 1996, and does not understand SHA256 because SHA256 was not around back then.
The way to check and handle this gracefully would be:
public byte[] SignData(RSACryptoServiceProvider csp, byte[] bytes)
{
byte[] sig = null;
if ((csp.CspKeyContainerInfo.ProviderType == PROV_RSA_FULL || csp.CspKeyContainerInfo.ProviderType == PROV_RSA_SCHANNEL) && !csp.CspKeyContainerInfo.HardwareDevice)
{
var cspParameters = new CspParameters
{
KeyContainerName = csp.CspKeyContainerInfo.KeyContainerName,
KeyNumber = csp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2,
};
using (var csp2 = new RSACryptoServiceProvider(cspParameters))
{
sig = csp2.SignData(bytes, CryptoConfig.MapNameToOID("SHA256"));
}
}
else {
sig = csp.SignData(bytes, CryptoConfig.MapNameToOID("SHA256"));
}
return sig;
}
FYI the CryptoAPI is being deprecated in favor of Cryptography API: Next Generation. One way to do what you want with CNG in C# would be with System.Security.Cryptography.Cng:
...
using (RSA rsa = new RSACng())
{
byte[] signature = rsa.SignData(message, hashAlgorithm, paddingMode);
...
}
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.
My application will take a set of files and sign them. (I'm not trying to sign an assembly.) There is a .p12 file that I get the private key from.
This is the code I was trying to use, but I get a System.Security.Cryptography.CryptographicException "Invalid algorithm specified.".
X509Certificate pXCert = new X509Certificate2(#"keyStore.p12", "password");
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)pXCert.PrivateKey;
string id = CryptoConfig.MapNameToOID("SHA256");
return csp.SignData(File.ReadAllBytes(filePath), id);
According to this answer it can't be done (the RSACryptoServiceProvider does not support SHA-256), but I was hoping that it might be possible using a different library, like Bouncy Castle.
I'm new to this stuff and I'm finding Bouncy Castle to be very confusing. I'm porting a Java app to C# and I have to use the same type of encryption to sign the files, so I am stuck with RSA + SHA256.
How can I do this using Bouncy Castle, OpenSSL.NET, Security.Cryptography, or another 3rd party library I haven't heard of? I'm assuming, if it can be done in Java then it can be done in C#.
UPDATE:
this is what I got from the link in poupou's anwser
X509Certificate2 cert = new X509Certificate2(KeyStoreFile, password");
RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)cert.PrivateKey;
CspParameters cspParam = new CspParameters();
cspParam.KeyContainerName = rsacsp.CspKeyContainerInfo.KeyContainerName;
cspParam.KeyNumber = rsacsp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2;
RSACryptoServiceProvider aescsp = new RSACryptoServiceProvider(cspParam);
aescsp.PersistKeyInCsp = false;
byte[] signed = aescsp.SignData(File.ReadAllBytes(file), "SHA256");
bool isValid = aescsp.VerifyData(File.ReadAllBytes(file), "SHA256", signed);
The problem is that I'm not getting the same results as I got with the original tool. As far as I can tell from reading the code the CryptoServiceProvider that does the actual signing is not using the PrivateKey from key store file. Is that Correct?
RSA + SHA256 can and will work...
Your later example may not work all the time, it should use the hash algorithm's OID, rather than it's name. As per your first example, this is obtained from a call to CryptoConfig.MapNameToOID(AlgorithmName) where AlgorithmName is what you are providing (i.e. "SHA256").
First you are going to need is the certificate with the private key. I normally read mine from the LocalMachine or CurrentUser store by using a public key file (.cer) to identify the private key, and then enumerate the certificates and match on the hash...
X509Certificate2 publicCert = new X509Certificate2(#"C:\mycertificate.cer");
//Fetch private key from the local machine store
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach( X509Certificate2 cert in store.Certificates)
{
if (cert.GetCertHashString() == publicCert.GetCertHashString())
privateCert = cert;
}
However you get there, once you've obtained a certificate with a private key we need to reconstruct it. This may be required due to the way the certificate creates it's private key, but I'm not really sure why. Anyway, we do this by first exporting the key and then re-importing it using whatever intermediate format you like, the easiest is xml:
//Round-trip the key to XML and back, there might be a better way but this works
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
Once that is done we can now sign a piece of data as follows:
//Create some data to sign
byte[] data = new byte[1024];
//Sign the data
byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));
Lastly, the verification can be done directly with the certificate's public key without need for the reconstruction as we did with the private key:
key = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig))
throw new CryptographicException();
The use of privateKey.toXMLString(true) or privateKey.exportParameters(true) aren't usable in a secure environment, since they require your private key to be exportable, which is NOT a good practice.
A better solution is to explicitly load the "Enhanced" crypto provider as such:
// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName);
privKey = new RSACryptoServiceProvider(cspparams);
This is how I dealt with that problem:
X509Certificate2 privateCert = new X509Certificate2("certificate.pfx", password, X509KeyStorageFlags.Exportable);
// This instance can not sign and verify with SHA256:
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey;
// This one can:
RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
privateKey1.ImportParameters(privateKey.ExportParameters(true));
byte[] data = Encoding.UTF8.GetBytes("Data to be signed");
byte[] signature = privateKey1.SignData(data, "SHA256");
bool isValid = privateKey1.VerifyData(data, "SHA256", signature);
I settled on changing the key file to specify the appropriate Crypto Service Provider, avoiding the issue in .NET altogether.
So when I create a PFX file out of a PEM private key and a CRT public certificate, I do it as follows:
openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx
The key part being -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider".
(-inkey specifies the private key file and -in specifies the public certificate to incorporate.)
You may need to tweak this for the file formats you have on hand. The command line examples on this page can help with that:
https://www.sslshopper.com/ssl-converter.html
I found this solution here:
http://hintdesk.com/c-how-to-fix-invalid-algorithm-specified-when-signing-with-sha256/
Use can use this on more recent frameworks.
public byte[] GetSignature(byte[] inputData)
{
using (var rsa = this.signingCertificate.GetRSAPrivateKey())
{
return rsa.SignData(inputData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
public bool ValidateSignature(byte[] inputData, byte[] signature)
{
using (var rsa = this.signingCertificate.GetRSAPublicKey())
{
return rsa.VerifyData(inputData, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
The signingCertificate above is a X509Certificate2 with a private key. This method does not require you to import any existing keys and works in a secure environment.
When you use a certificate to get your RSACryptoServiceProvider it really matters what's the underlying CryptoAPI provider. By default, when you create a certificate with 'makecert', it's "RSA-FULL" which only supports SHA1 hashes for signature. You need the new "RSA-AES" one that supports SHA2.
So, you can create your certificate with an additional option: -sp "Microsoft Enhanced RSA and AES Cryptographic Provider" (or an equivalent -sy 24) and then your code would work without the key juggling stuff.
Here is how I signed a string without having to modify the certificate (to a Microsoft Enhanced RSA and AES Cryptographic provider).
byte[] certificate = File.ReadAllBytes(#"C:\Users\AwesomeUser\Desktop\Test\ServerCertificate.pfx");
X509Certificate2 cert2 = new X509Certificate2(certificate, string.Empty, X509KeyStorageFlags.Exportable);
string stringToBeSigned = "This is a string to be signed";
SHA256Managed shHash = new SHA256Managed();
byte[] computedHash = shHash.ComputeHash(Encoding.Default.GetBytes(stringToBeSigned));
var certifiedRSACryptoServiceProvider = cert2.PrivateKey as RSACryptoServiceProvider;
RSACryptoServiceProvider defaultRSACryptoServiceProvider = new RSACryptoServiceProvider();
defaultRSACryptoServiceProvider.ImportParameters(certifiedRSACryptoServiceProvider.ExportParameters(true));
byte[] signedHashValue = defaultRSACryptoServiceProvider.SignData(computedHash, "SHA256");
string signature = Convert.ToBase64String(signedHashValue);
Console.WriteLine("Signature : {0}", signature);
RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = cert2.PublicKey.Key as RSACryptoServiceProvider;
bool verify = publicCertifiedRSACryptoServiceProvider.VerifyData(computedHash, "SHA256", signedHashValue);
Console.WriteLine("Verification result : {0}", verify);
According to this blog it should work with FX 3.5 (see note below). However it's important to recall that most of .NET cryptography is based on CryptoAPI (even if CNG is being more and more exposed in recent FX releases).
The key point is that CryptoAPI algorithm support depends on the Crypto Service Provider (CSP) being used and that varies a bit between Windows versions (i.e. what's working on Windows 7 might not work on Windows 2000).
Read the comments (from the blog entry) to see a possible workaround where you specify the AES CSP (instead of the default one) when creating your RSACCryptoServiceProvider instance. That seems to work for some people, YMMV.
Note: this is confusing to many people because all the released .NET frameworks includes a managed implementation of SHA256 which cannot be used by CryptoAPI. FWIW Mono does not suffer from such issues ;-)
I know this is an old thread but for those still stuck in the past and looking for an answer, the following worked for me based off #BKibler's answer. The comments stated it's not using the correct key and it's because the solution is missing a couple key settings.
// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
if (!Enum.TryParse<KeyNumber>(privKey.CspKeyContainerInfo.KeyNumber.ToString(), out var keyNumber))
throw new Exception($"Unknown key number {privKey.CspKeyContainerInfo.KeyNumber}");
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName)
{
KeyNumber = (int)keyNumber,
Flags = CspProviderFlags.UseExistingKey
};
privKey = new RSACryptoServiceProvider(cspparams);
You need to set both "KeyNumber" and "Flags" so the existing (non-exportable) key is used and you can use the public key from the certificate to verify.
I have noticed similar issues in .NET with the wrong private key being used (or was it flat-out errors? I do not recall) when the certificate I am working with is not in the user/computer certificate store. Installing it into the stored fixed the problem for my scenario and things started working as expected - perhaps you can try that.