Use a PFX File X509 for Encrypt/Decrypt - c#

I have a pfx file that is for test purposes and have no special security.
I need to use this file to encrypt a XML file. Scenario is that I need to send a Password-Zip file to a client along with a Metadata file relating to the Zip. The Zip will actually contain file for updating a software installation. At first, i though I'd encrypt the Zip itself. But then, it was decided that it'll be protected with a simple password and will be uploaded to a web site (ASP.NET) at client's. The client will also upload a metadata file that will be given with the Zipped update. This Metadata file will have stuff like Client identifier, MAC Address, MD5 Hash of update zip file etc... I need to encrypt this metadata file before sending and decrypt once the client uploads both ZIP and Meta. Once decrypted, I'll try to verify client and the current installation and the patch itself.
The PFX is generated for me.
What will I need to extract from the PFX
I can use BouncyCastle here.
The idea was that once the client install the software at his end, he'll generate a X509 certificate request. That will create two thing: the request and a private key. The client will keep the private key and send us the request. We'll validate the client (payment and all) and generate a certificate for him and email him back (the PFX). He'll then import it to unlock the features on the software and be able to get zipped updates and further stuff
The question is a bit of a complete solution demand like but I'm facing a real hard time then it comes to signing with X509. I'm looking at BouncyCastle docs right now.
Thanks for the read. Any help will be appreciated.

sry bout the long answer.
Yes you can use bouncycastle to extract a certificate from a pfx.
var pkcs = new Pkcs12Store(File.Open("path.pfx", FileMode.Open), "password".ToCharArray());
pkcs.Aliases // is a list of certificate names that are in the pfx;
pkcs.GetCertificate(alias); // gets a certificate from the pfx
We use a very similar approach but we send the certificate without the private key back in PEM, since it doesn't contain any "secret's"
This is how you make a certificate signing request:
//generate a privatekey and public key
RsaKeyPairGenerator rkpg1 = new RsaKeyPairGenerator();
rkpg1.Init(new KeyGenerationParameters(new SecureRandom(), Keystrength));
AsymmetricCipherKeyPair ackp1 = rkpg1.GenerateKeyPair();
RsaPrivateCrtKeyParameters privateKey = (RsaPrivateCrtKeyParameters)ackp1.Private;
RsaKeyParameters publicKey = (RsaKeyParameters)ackp1.Public;
X509Name comonname = new X509Name (cname);
Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest ("SHA1WITHRSA", comonname, publicKey, null, privateKey);
csr.Verify ();
StringBuilder sb = new StringBuilder();
PemWriter pw = new PemWriter (new StringWriter (sb));
pw.WriteObject (csr);
pw.Writer.Flush ();
var pemstring = sb.ToString ();
This is what happens at the server end signing the certificate signing request:
As far as I understand the root certificate can be any certificate of which you have a private key. And if you want to use it in other application like a browser it needs to be inserted in to the certificate store called "Trusted Root Certificate Authorities", if it is a self signed certificate, or "Trusted Intermediate Certificate Authorities", if it isn't self signed at the client computer without the pirvate key off course.
A certificate with a private key is identifiable by the little key added to the icon see here;
//rootCert contains the rootcertificate in bouncycastle format
var pemstring = "a string containing the PEM";
PemReader pr = new PemReader (new StringReader (pemstring));
Pkcs10CertificationRequest csr = (Pkcs10CertificationRequest)pr.ReadObject ();
X509V3CertificateGenerator certgen = new X509V3CertificateGenerator();
certgen.SetSubjectDN(csr.GetCertificationRequestInfo().Subject);
certgen.SetIssuerDN(rootCert.SubjectDN);
certgen.SetPublicKey(csr.GetPublicKey());
certgen.SetSignatureAlgorithm(csr.SignatureAlgorithm.ObjectID.Id);
certgen.SetNotAfter(validThrough); //a datetime object
certgen.SetNotBefore(validFrom); //a datetime object
certgen.SetSerialNumber(serialNumber); //a biginteger
X509Certificate clientcert = certgen.Generate(rootPrivateKey);
//to send the certificate without the private key to the client you'll have to
//convert it to PEM:
StringBuilder sb = new StringBuilder();
PemWriter pw = new PemWriter (new StringWriter (sb));
pw.WriteObject (clientcert);
pw.Writer.Flush ();
var pemstring = sb.ToString ();
//to make it a .net certificate use:
X509Certificate2 netcert = DotNetUtilities.ToX509Certificate (clientcert);
The client certificatie does NOT contain a private key atm. Thats what happens at the client end again like so:
//where pemstring contains the certificate in a PEMstring like shown above.
//and privateKey is the one we had in the first part over at the client.
PemReader pr = new PemReader(new StringReader(pemstring));
X509Certificate2 cert = DotNetUtilities.ToX509Certificate((Bouncy.X509Certificate)pr.ReadObject());
CspParameters cparms = new CspParameters
{
CryptoKeySecurity = new CryptoKeySecurity(),
Flags = CspProviderFlags.UseMachineKeyStore
};
RSACryptoServiceProvider rcsp = new RSACryptoServiceProvider(cparms);
RSAParameters parms = new RSAParameters
{
Modulus = privateKey.Modulus.ToByteArrayUnsigned (),
P = privateKey.P.ToByteArrayUnsigned (),
Q = privateKey.Q.ToByteArrayUnsigned (),
DP = privateKey.DP.ToByteArrayUnsigned (),
DQ = privateKey.DQ.ToByteArrayUnsigned (),
InverseQ = privateKey.QInv.ToByteArrayUnsigned (),
D = privateKey.Exponent.ToByteArrayUnsigned (),
Exponent = privateKey.PublicExponent.ToByteArrayUnsigned ()
};
rcsp.ImportParameters(parms);
netcert.PrivateKey = rcsp;

Related

I want to create a p12 file based on a cert file and a key file

Both the cert and the key file are of type string.
I tried using this:
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
rsaKey.ImportParameters(keyfile);
X509Certificate2 cert = new X509Certificate2(certfile);
cert.PrivateKey = rsaKey;
cert.Export(X509ContentType.Pkcs12, "xyz");
RestAsynchronicClient client = new RestAsynchronicClient(url, RestDataStandard.JSON, null, cert, logger);
Here I am getting conversion error in the second line. Conversion error from string to RSAParameter.
There's no automatic conversion from a file path, or file contents, to an RSAParameters; the RSACryptoServiceProvider is out of date and not recommended for new code, and the PrivateKey property on certificates is fully [Obsolete] in new versions of .NET.
With .NET 5+, this is easy:
byte[] pfxBytes;
using (X509Certificate2 cert = X509Certificate2.CreateFromPemFile(certFile, keyFile))
{
pfxBytes = cert.Export(X509ContentType.Pkcs12, pfxPwd);
}
Or, in the style closer to the code you've written:
byte[] pfxBytes;
using (X509Certificate2 cert = new X509Certificate2(certFile))
using (RSA key = RSA.Create())
{
key.ImportFromPem(File.ReadAllText(keyFile));
using (X509Certificate2 certWithKey = cert.CopyWithPrivateKey(key))
{
pfxBytes = certWithKey.Export(X509ContentType.Pkcs12, pfxPwd);
}
}
Your reference snippet then goes on to ignore the PFX/PKCS12 output and pass the cert to RestAsynchronicClient. Because of some idiosyncrasies on Windows, that generally won't work if you load the cert from this style. But, if you load the PFX into a new X509Certificate2 object, that'll be in a slightly different state and everything'll be happy.
RestAsynchronicClient client = new RestAsynchronicClient(
url,
RestDataStandard.JSON,
null,
new X509Certificate2(pfxBytes, pfxPwd),
logger);

C# How to S/Mime in Asp.net 5 with Bouncy Castle instead of x509certificate2

I successfully used the below code to send an S/Mime e-mail on latest Windows Server, but it fails on 2012 Windows Server. I think 2012 does not support newer AES encryption.
How would I rewrite the following code to replace
System.Security.Cryptography.X509Certificates.X509Certificate2() with Bouncy Castle, also using MimeKit and Mailkit? I tried using older DES3 for 2012, but it fails with CryptoThrowHelper+WindowsCryptographicException: The specified network password is not correct. Exactly the same code runs just fine on Windows 10 on my developer machine. Ultimately I want this to run in a Linux container, plus I think Bouncy Castle is much more fun to type and say. :)
var message = new MimeMessage(); // Using MimeKit, MailKit
message.To.Add( new MailboxAddress("John Doe","jdoe#somewhere.com"));
message.From.Add( new MailboxAddress("Jane Doe","jndoe#somewhere.com"));
message.Headers.Add( "AS3-From", "PILM");
message.Headers.Add( "AS3-To", "SARS");
message.Date = DateTimeOffset.Now;
message.MessageId = "42";
message.Subject = "This is a subject";
message.Body = new TextPart("html") { Text = "This is a body" };
using (var context = new TemporarySecureMimeContext())
{ // TODO: Replace Microsoft Cryptography with BouncyCastle
var cert = new System.Security.Cryptography
.X509Certificates.X509Certificate2(
#"c:\security\smime.p12", "VeryCoolPassword",
System.Security.Cryptography.X509Certificates
.X509KeyStorageFlags.EphemeralKeySet);
var recip = new CmsRecipient(cert) {
EncryptionAlgorithms = new EncryptionAlgorithm[] {
EncryptionAlgorithm.TripleDes }
};
var recips = new CmsRecipientCollection();
recips.Add(recip);
message.Body = ApplicationPkcs7Mime.Encrypt(
context, recips, message.Body );
};
var client = new SmtpClient();
client.Connect("MySuperEmailServer", 465, true);
client.Authenticate("MySuperUserName", "VeryCoolPassword");
client.Send(message);
client.Disconnect(true);
client.Dispose();
There seems to be several parts to this question.
First, you want to know how to load a S/MIME certificate using BouncyCastle. Since, as Crypt32 pointed out, you aren't likely to need to load *.pfx (aka *.p12) files since you wouldn't have their private key, you'll likely need to load a *.cer (or *.crt) file:
using (var stream = File.OpenRead ("smime-recipient-certificate.cer")) {
var parser = new X509CertificateParser ();
var certificate = parser.ReadCertificate (stream);
}
MimeKit's CmsRecipient class will do this for you if you call the CmsRecipient .ctor that takes a fileName argument.
That said, it never hurts to know how to load it manually in case you maybe don't have it stored on disk somewhere (e.g. maybe a MemoryStream or something).
The CmsRecipient .ctor that takes an X509Certificate2 (that you are currently using) is also fine to use. MimeKit just converts the X509Certificate2 certificate into a BouncyCastle certificate internally.
That said, based on your conversation with Crypt32, it sounds like the issue you were having is with loading an X509Certificate2 from a .pfx file on Windows Server 2012? In which case, loading the certificate using BouncyCastle is probably the way to go.
If you need or want to know how to load a .pfx file using BouncyCastle, you can do that using the following logic:
// Note: BouncyCastle's X509Certificate class is named X509Certificate, not to be
// confused with System.Security.Cryptography.X509Certificates.X509Certificate.
X509Certificate LoadPfx (Stream stream, string password, out AsymmetricKeyParameter privateKey)
{
var pkcs12 = new Pkcs12Store (stream, password.ToCharArray ());
foreach (string alias in pkcs12.Aliases) {
if (!pkcs12.IsKeyEntry (alias))
continue;
var chain = pkcs12.GetCertificateChain (alias);
if (chain.Length == 0)
continue;
var keyEntry = pkcs12.GetKey (alias);
if (!keyEntry.Key.IsPrivate)
continue;
privateKey = keyEntry.Key;
return chain[0].Certificate;
}
throw Exception ("Did not find a certificate with a private key.");
}
Note: I would probably suggest changing this:
message.MessageId = "42";
to this:
message.MessageId = MimeUtils.GenerateMessageId();
This will generate a random/unique message-id value using the correct syntax (The Message-Id header isn't a number, it's a xyz#abc.com type value).

How do I use a .key file in C#?

I am setting up AS2 communication for signing files to a customer. As I understand signing to work through AS2 communication, we will sign the data we send with our private key, and they will verify it with our public key.
My problem:
My IT department has given me a .cer and a .key file. The .cer file has no private key in it, and obviously the .key file is the private key. The customer will add the .cer file to their trusted root to verify our messages. I am having trouble understanding how to sign my data with the .key file. It's not something I can add to a Personal certificate store, so I can't simply get the certificate and do this:
//Open my Personal cert store.
X509Store my = new X509Store(StoreName.My, StoreLocation.LocalMachine);
my.Open(OpenFlags.ReadOnly);
RSACryptoServiceProvider csp = null;
foreach (X509Certificate2 cert in my.Certificates)
{
if (cert.Subject.Contains("My certificate subject"))
{
// We found it.
// Get its associated CSP and private key
csp = (RSACryptoServiceProvider)cert.PrivateKey;
}
}
// Hash the data
SHA256Managed sha256 = new SHA256Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] hash = sha256.ComputeHash(arMessage);
// Sign the hash
return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
How do I get my private key as a RSACryptoServiceProvider directly using the .key file, since a .key file can't be stored in the certificate store?
I know only how to proceed with PFX files, but converting your KEY file to PFX should not be a problem. See this post:
Is it possible to convert an SSL certificate from a .key file to a .pfx?
Later on, you can manually open the certificate file using this method:
// This you should know
var certPath = #"path-to-file.pfx";
var certPass = #"password-goes-here";
// Create a collection object and populate it using the PFX file
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(certPath, certPass, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
//Then you can iterate over the collection:
foreach (X509Certificate2 cert in collection)
{
// Bingo
// Here you can do whatever you want with "cert"
}
I've done similar things in a slightly different way. Forgive me if this is not helpful at all and I can delete it.
I'm not doing any signing here but you might be able to figure out how from this.
var collection = new X509Certificate2Collection();
collection.Import(System.Web.Hosting.HostingEnvironment.MapPath(pathToPrivateKey), privateKeyPassword, X509KeyStorageFlags.MachineKeySet);
var requestToPing = (HttpWebRequest)WebRequest.Create(dropOffURL);
requestToPing.Method = "POST";
requestToPing.PreAuthenticate = true;
requestToPing.ClientCertificates.Add(collection[0]);

Create CRL file with Bouncy Castle c#

I've build my own root CA certificate with Bouncy Castle, and I'm using it to build other certificates. I want to build a Certificate Revocation List (CRL) to include the list of revoqued certificates, using Bouncy Castle C#. Example:
//Retrieve CA root certificate
X509Store CAstore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
CAstore.Open(OpenFlags.ReadWrite | OpenFlags.OpenExistingOnly);
X509Certificate2Collection x509Certificate2Collection =
CAstore.Certificates.Find(X509FindType.FindBySerialNumber,
this.textBoxSerialCA.Text, true);
X509Certificate2 cert = x509Certificate2Collection[0];
var certCA = DotNetUtilities.FromX509Certificate(cert);
CAstore.Close();
X509V2CrlGenerator crlGen = new X509V2CrlGenerator();
crlGen.SetIssuerDN(certCA.IssuerDN);
crlGen.SetThisUpdate(DateTime.Now);
crlGen.SetNextUpdate(DateTime.Now.AddYears(1));
crlGen.SetSignatureAlgorithm("SHA1withRSA");
crlGen.AddCrlEntry(BigInteger.One, DateTime.Now, CrlReason.PrivilegeWithdrawn);
crlGen.AddExtension(X509Extensions.AuthorityKeyIdentifier,
false,
new AuthorityKeyIdentifierStructure(certCA));
crlGen.AddExtension(X509Extensions.CrlNumber,
false,
new CrlNumber(BigInteger.One));
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
var Akp = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(cert.PrivateKey).Private;
X509Crl crlTemp = crlGen.Generate(Akp,random);
All is OK until this point. How can I save the X509Crl object into a .crl file?
Best regards.
This answer comes quite late, but you can use the PemWriter class in Bouncy Castle to write to a PEM file.
PemWriter pemWriter = new PemWriter(new StreamWriter(File.Open(fileName, FileMode.Create)));
pemWriter.WriteObject(crlTemp);
pemWriter.Writer.Flush();
pemWriter.Writer.Close();
In BouncyCastle.Crypto version 1.7.4114.6375, I was able to take your code and simply add:
var b = crlTemp.GetEncoded();
System.IO.File.WriteAllBytes(#"C:\temp\test.crl", b);
Then, in Windows, double clicking on the 'test.crl' file will open the standard, built-in Certificate Revocation List dialog without any errors and all the information looks correct when compared to other CRL files.
And after you've got a CRL in PEM format you can convert it via openssl with the following command:
openssl crl -in list.pem -outform der -out list.crl

signing with x509certificate that has signature algorithm sha256 [duplicate]

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.

Categories