I would like to use Public/Private encryption where my web app would sign an xml document or a piece of data using a private key and it will be verified in the application using the public key,
Is it possible to use RSACryptoServiceProvider in .net 2.0 hosted environment if not what are other possible workarounds?
Yes, quite possible.
If you export the cert you will use for data-signing into a .pfx file, you can get a digsig this way.
using System;
using System.Xml;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
// ...
static void SignWithPfxPrivateKey()
{
X509Certificate2 certificate = new X509Certificate2(certFile, pfxPassword);
RSACryptoServiceProvider rsaCsp = (RSACryptoServiceProvider) certificate.PrivateKey;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;
if (loadFromString)
xmlDoc.LoadXml(rawXml); // load from a string
else
xmlDoc.Load("test.xml"); // load from a document
// Sign the XML document.
SignXml(xmlDoc, rsaCsp);
// Save the document.
xmlDoc.Save("RsaSigningWithCert.xml");
xmlDoc.Save(new XTWFND(Console.Out));
}
public static void SignXml(XmlDocument Doc, RSA Key)
{
// Check arguments.
if (Doc == null)
throw new ArgumentException("Doc");
if (Key == null)
throw new ArgumentException("Key");
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(Doc);
// Add the key to the SignedXml document.
signedXml.SigningKey = Key;
// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "";
// Add an enveloped transformation to the reference.
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
Doc.DocumentElement.AppendChild(Doc.ImportNode(xmlDigitalSignature, true));
}
To use this, you will have to upload a .pfx file to the hosted server. You also need a password for that pfx file, which you should store securely in application config settings. You can do something similar with a .cer file.
You can also load the RSA key from regular XML. If you have an RSACryptoServiceProvider that you instantiated by loading a key from your local machine store, like this:
// Get the key pair from the key store.
CspParameters parms = new CspParameters(1);
parms.Flags = CspProviderFlags.UseMachineKeyStore;
parms.KeyContainerName = "SnapConfig";
parms.KeyNumber = 2;
RsaCsp = new RSACryptoServiceProvider(parms);
...then you can export that key with
RsaCsp.ToXmlString(true);
The result is a string, containing an XML document that you can save to a file, and then upload to the hosted server. An alternative to the .pfx file as a store for your key pair. Be careful. This xml document is not protected with a password, and is insecure. (read the doc on ToXmlString()) You can encrypt this XML doc, just as you would encrypt any other settings. (search)
In that case you can do this to get your csp:
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.FromXmlString(keyPairXml);
See here: http://www.codeproject.com/KB/security/EZRSA.aspx
Yes, it is possible to use this in a hosted environment, but beware of the multi threaded issues relating to its use
These are some suggestions to minimize threading issues in ASP.NET:
Set CspParameters.KeyContainer name when creating an RSACryptoServiceProvider object. Never use the default one, NEVER!
This is prone to store corruption. We talked about this already.
Explicitely call Dispose on the RSACrytpoServiceProvider object when possible. This way, we don't rely on the Garbage Collector to
"eventually" release the CSP.
Set UseMachineKeyStore in CspParameters.Flags when creating an RSACryptoServiceProvider. This flag tells RSACryptoServiceProvider to
use "machine location" for storing RSA public/private key pair instead
of the "current user" location. This is equivalent to calling
CryptAcquireContext API with CRYPT_MACHINE_KEYSET. Make sure all the
threads using the RSA instance are running under the same account.
Don't set RSACryptoServiceProvider.PersistKeyInCSP to False. This will cause the key container to be deleted when RSA instance is
released or garbage collected. If multiple threads are trying to
access the same named key container, the state of that named key
container could be in a temporary cleaned up state and it cannot be
acquired.
Another option would be for us to instantiate RSACryptoServiceProvider once as part of Web Application
initialization and then use that same instance for encryption or
decryption under a critical section like lock (i.e. Mutex). This would
also avoid reacquiring key container multiple times from the same
process.
Related
I'm attempting to apply an rsa-sha512 signature to a message, using a certificate on the local HDD.
The final SignData raises a cryptographic exception "Invalid algorithm specified".
However, if I use SignData on a new instance of RSACryptoServiceProvider (created by importing an export of the original RSACryptoServiceProvider), I don't get that exception.
Is there some reason that the original version raises the exception? Since the "copy" is evidently different, I'd prefer to use the original.
The c# code I'm using is as follows:
X509Certificate2 cert = new X509Certificate2("C:\\Certs\\" + certName + ".p12", certPassword, X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PrivateKey;
UTF8Encoding ByteConverter = new UTF8Encoding();
byte[] unsignedBytes = ByteConverter.GetBytes(unsignedText);
byte[] signature;
//This raises an exception, "Invalid algorithm specified."
signature = csp.SignData(unsignedBytes, new SHA512CryptoServiceProvider());
//But if I make a copy of the RSACryptoServiceProvider, no exception is raised
RSACryptoServiceProvider cspCopy = new RSACryptoServiceProvider();
RSAParameters Key = csp.ExportParameters(true);
cspCopy.ImportParameters(Key);
signature = cspCopy.SignData(unsignedBytes, new SHA512CryptoServiceProvider());
To quote myself from a previous answer:
Software-backed RSACryptoServiceProvider is only capable of doing RSA signature using a SHA-2 digest algorithm when the CSP ProviderType value is 24 (PROV_RSA_AES) and the ProviderName is "Microsoft Enhanced RSA and AES Cryptographic Provider" (MS_ENH_RSA_AES_PROV). Hardware may or may not require PROV_RSA_AES, just depends on the hardware.
In this case, the PFX identifies the private key to belong to an older CSP (or, maybe, it has no CSP identifier and the PFX import is picking bad defaults). For software keys it's possible to extract the CspParameterInfo data from the key and re-open it using ProviderType 24, which is one way to work around the problem. Exporting the raw RSA parameters and importing it into a new object (which defaults to ProviderType 24) is a more aggressive workaround.
The better way to work around the problem is to abandon RSACryptoServiceProvider. Instead of using cert.PrivateKey, use cert.GetRSAPrivateKey(), which will almost always return an RSACng instance, which doesn't have this problem (but don't cast it if you can avoid it (if nothing else, see "almost" always)).
byte[] signature;
using (RSA rsa = cert.GetRSAPrivateKey())
{
signature = rsa.SignData(
unsignedBytes,
HashAlgorithmName.SHA512,
RSASignaturePadding.Pkcs1);
}
The using statement is correct for GetRSAPrivateKey, because it returns a distinct object per call.
RSACng and GetRSAPrivateKey both require .NET 4.6, but that's more than two years old at this point (and 4 (and a half) newer releases have happened in that time), so shouldn't cause you difficulty as a dependency.
I need to convert a generated BouncyCastle X509Certificate to X509Certificate2 with the private key getting loaded in the resulting .NET Standard X509Certificate2 object. Is that possible?
There is an answer for a similar question https://stackoverflow.com/a/8150799/5048935 , but after generating a BouncyCastle certificate and converting it to .NET X509Certificate2 the resulting object has its HasPrivateKey property set to "false".
Context
I need to load a certificate and a private key (separate Base64 strings without headers and footers) to a X509Certificate2 object to pass it on to the app from a .NET Standard 1.6 library. The problem is, there is no PrivateKey property in the X509Certificate2 class in .NET Standard! So the only way for me to actually load the private key in a X509Certificate2 object is to combine it with the certificate itself in a .pfx file and load it like that in a constructor.
I've got suggestions to use BouncyCastle for that ( https://stackoverflow.com/a/44465965/5048935 ), so I first generate a BouncyCastle X509Certificate from the Base64 strings and then try to convert it to X509Certificate2 while retaining the private key.
The best way I've found is to go through an in-memory PFX stream. Assuming you've already loaded your Bouncy Castle cert in bcCert. Note: If you are going to be saving the .NET X509Certificate2 anywhere, the "alias" is what it's going to be called in the UI later, otherwise it's irrelevant (other than it needs to be the same for both calls).
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using System.IO;
using System.Security.Cryptography.X509Certificates
var pkcs12Store = new Pkcs12Store();
var certEntry = new X509CertificateEntry(bcCert);
pkcs12Store.SetCertificateEntry(alias, certEntry);
pkcs12Store.SetKeyEntry(alias, new AsymmetricKeyEntry(certKey.Private), new[] { certEntry });
X509Certificate2 keyedCert;
using (MemoryStream pfxStream = new MemoryStream())
{
pkcs12Store.Save(pfxStream, null, new SecureRandom());
pfxStream.Seek(0, SeekOrigin.Begin);
keyedCert = new X509Certificate2(pfxStream.ToArray());
}
I want to create 2 really simple dlls:
1) that will sign an xml document
2) that will check that the xml document hasnt been modified.
I tried using the RSACryptoServiceProvider and a key container. But when I move to a different machine this does not work as the key is being stored in the machine.
I want to store the key in the dlls im creating (I know this is not reccomended) but I just cannot work out how to write some code to simply sign an xml document and then verify that it hasn't been changed.
So do I need to use symmetric key to do what I want is this possible?
Pete
You already mention the problems with storing the private key in the dll, so I won't repeat that.
Do this:
On your own machine run this code:
var key = new RSACryptoServiceProvider(2048);
string publicKey = key.ToXmlString(false);
string privateKey = key.ToXmlString(true);
Console.WriteLine(publicKey);
Console.WriteLine(privateKey);
this outputs two (long) lines. Copy those into your code:
Sign:
var privateKey = new RSACryptoServiceProvider();
privateKey.FromXmlString(/* insert the private-key XML string here */ );
privateKey.SignData(/*...*/);
Verify:
var publicKey = new RSACryptoServiceProvider();
publicKey.FromXmlString(/* insert the public-key XML string here */ );
publicKey.VerifyData(/*...*/);
If it is just about to verify that your xml document hasn't been modified a simple MD5 checksum (or any other good hashing algorithm) would be easier to implement and is what you need. It would be also verifyable on different machines.
I am using the RSACryptoServiceProvider like this...
private byte[] RSAEncrypt(byte[] DataToEncrypt, string ContainerName, bool DoOAEPPadding)
{
try
{
byte[] encryptedData;
// Create a new instance of CspParameters. Pass
// 13 to specify a DSA container or 1 to specify
// an RSA container. The default is 1.
CspParameters cspParams = new CspParameters();
// Specify the container name using the passed variable.
cspParams.KeyContainerName = ContainerName;
cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
//Create a new instance of RSACryptoServiceProvider.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(cspParams))
{
//Encrypt the passed byte array and specify OAEP padding.
//OAEP padding is only available on Microsoft Windows XP or
//later.
encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
}
return encryptedData;
}
//Catch and display a CryptographicException
//to the console.
catch (CryptographicException ex)
{
sl.Write(ex, MessageType.Error);
throw;
}
}
I then try to decrypt the data after turning off my Outlook Plugin Windows Form application and turning it back on which is what is using this peice of code. The decrypt code looks like this...
private byte[] RSAEncrypt(byte[] DataToEncrypt, string ContainerName, bool DoOAEPPadding)
{
try
{
byte[] encryptedData;
// Create a new instance of CspParameters. Pass
// 13 to specify a DSA container or 1 to specify
// an RSA container. The default is 1.
CspParameters cspParams = new CspParameters();
// Specify the container name using the passed variable.
cspParams.KeyContainerName = ContainerName;
cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
//Create a new instance of RSACryptoServiceProvider.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(cspParams))
{
//Encrypt the passed byte array and specify OAEP padding.
//OAEP padding is only available on Microsoft Windows XP or
//later.
encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
}
return encryptedData;
}
//Catch and display a CryptographicException
//to the console.
catch (CryptographicException ex)
{
sl.Write(ex, MessageType.Error);
throw;
}
}
Works great until something comes up that I can not put my finger on. I don't know if it is like the date changes or what. What happens is that I try to decrypt the data and I get a "bad data" error. Now again it works great until some elapsed period of time, or turning off the app, or the user logging off. I just don't know and can't determine what causes it. The moment I blow away the encrypted data which comes from a text file and recreate it and decrypt it I have no problem. Even if I restart the application in between encrypting/saving to file and the reading from file/decrypting it will works great! Something happens and I just don't know KeyContainers well enough to understand what could possibly make the CspParameters expire is my best guess?
You could try using the bouncy castle crypto libraries if you're really stuck:
http://www.bouncycastle.org/csharp/
I ended up using the CspParameters flag and instead of using the Users KeyContainer store I used the Machine KeyContainer Store.
Yes, if you set:
cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
then the key container is stored in the user's key container store, then logging on as another user will and using RSA with present you with an entirely different KeyContainer store.
Using this instead:
cspParams.Flags = CspProviderFlags.UseMachineKeyStore = true;
Will use the local machine's KeyContainer store, which is global for the machine, and will provide you with the same KeyContainer store, irregardless of which user is logged in. However, this only applies for that windows installation. Running your program under a different windows installation or machine will provide you with a different KeyContainer store. If you wish to decrypt the same data across multiple machines, you will need to persist your key to a file on the hard drive. Persisting a key to a plain text file is a huge security risk, so please encrypt your key before persisting it to a file, or put it in a password protected .rar files or something.
If your still having issues, try setting:
RSA.PersistKeyInCsp = true;
This will ensure that your key is persisted in the KeyContainer store. Persisting the file in the KeyContainer should be the default behavior if you use the CspParameters constructor such as:
CspParameters cspParams = new CspParameters();
In Microsoft's own words:
"This form of CspParameters initializes the ProviderType field to a value of 24, which specifies the PROV_RSA_AES provider."
Source: http://msdn.microsoft.com/en-us/library/xw9ywed4.aspx
So your comments in your code is incorrect and my be misleading you. I would advise you to correct them.
I am unsure about other ProviderTypes and their default settings regarding persisting the key in the KeyContainer store, so setting PersistKeyInCsp to TRUE might be necessary if your still having issues.
Hope this helps.
~Adam WhiteHat();
it's late, I'm tired, and probably being quite dense....
I have written an application that I need to secure so it will only run on machines that I generate a key for.
What I am doing for now is getting the BIOS serial number and generating a hash from that, I then am encrypting it using a XML RSA private key. I then sign the XML to ensure that it is not tampered with.
I am trying to package the public key to decrypt and verify the signature with, but every time I try to execute the code as a different user than the one that generated the signature I get a failure on the signature.
Most of my code is modified from sample code I have found since I am not as familiar with RSA encryption as I would like to be. Below is the code I was using and the code I thought I needed to use to get this working right...
Any feedback would be greatly appreciated as I am quite lost at this point
the original code I was working with was this, this code works fine as long as the user launching the program is the same one that signed the document originally...
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
// Create a new RSA signing key and save it in the container.
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams)
{
PersistKeyInCsp = true,
};
This code is what I believe I should be doing but it's failing to verify the signature no matter what I do, regardless if it's the same user or a different one...
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
//Load the private key from xml file
XmlDocument xmlPrivateKey = new XmlDocument();
xmlPrivateKey.Load("KeyPriv.xml");
rsaKey.FromXmlString(xmlPrivateKey.InnerXml);
I believe this to have something to do with the key container name (Being a real dumbass here please excuse me) I am quite certain that this is the line that is both causing it to work in the first case and preventing it from working in the second case....
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";
Is there a way for me to sign/encrypt the XML with a private key when the application license is generated and then drop the public key in the app directory and use that to verify/decrypt the code? I can drop the encryption part if I can get the signature part working right. I was using it as a backup to obfuscate the origin of the license code I am keying from.
Does any of this make sense?
Am I a total dunce?
Thanks for any help anyone can give me in this..
I used this method to sign xml documents using a private key stored in an xml file that I then embedded into the application .dll as a resource. I think you may be struggling with permissions accessing the keystore, and this would also create hassles transferring the code to other servers etc.
Here is the code to get the private key as an embedded resource and sign the document:
(Sign is the name of the class this method is located in, Licensing.Private.Private.xml is a combination of the default namespace + folder + filename of the resource)
public static void SignDocument(XmlDocument xmldoc)
{
//Get the XML content from the embedded XML privatekey.
Stream s = null;
string xmlkey = string.Empty;
try
{
s = typeof(Sign).Assembly.GetManifestResourceStream("Licensing.Private.Private.xml");
// Read-in the XML content.
StreamReader reader = new StreamReader(s);
xmlkey = reader.ReadToEnd();
reader.Close();
}
catch (Exception e)
{
throw new Exception("Error: could not import key:",e);
}
// Create an RSA crypto service provider from the embedded
// XML document resource (the private key).
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.FromXmlString(xmlkey);
//Creating the XML signing object.
SignedXml sxml = new SignedXml(xmldoc);
sxml.SigningKey = csp;
//Set the canonicalization method for the document.
sxml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationUrl; // No comments.
//Create an empty reference (not enveloped) for the XPath transformation.
Reference r = new Reference("");
//Create the XPath transform and add it to the reference list.
r.AddTransform(new XmlDsigEnvelopedSignatureTransform(false));
//Add the reference to the SignedXml object.
sxml.AddReference(r);
//Compute the signature.
sxml.ComputeSignature();
// Get the signature XML and add it to the document element.
XmlElement sig = sxml.GetXml();
xmldoc.DocumentElement.AppendChild(sig);
}
Use the following code the generate the private.xml and public.xml keys. Keep the private.xml file secure, obviously.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
File.WriteAllText(#"C:\privateKey.xml", rsa.ToXmlString(true)); // Private Key
File.WriteAllText(#"C:\publicKey.xml", rsa.ToXmlString(false)); // Public Key
Guess, the problem is that different users don't have access to the key that is stored for the 1st user (Please note: I am not a cryptography expert).