RSACryptoProvider generates same key repeatedly for same CspParameter ContainerName - c#

I'm new to the .NET CryptoProvider space, and am a little concerned by what I have seen regarding the repeated creation of the same key by the RSACryptoProvider.
I am using a container because I am storing the key off to file on the server, like so (I export the CspBlob subsequent to this creation and reimport it later)...
_cp = new CspParameters { KeyContainerName = ContainerName };
In this case the ContainerName has a hardcoded value that I reference the container by.
What's bothering me is that when I create the RSACryptoProvider, and by exentsion the key pair, the generated key values are always the same!
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(RSAKeySize, _cp);
If I change the name of the container, the key changes. There must be SOME other source of randomness than the container name when you create an RSACryptoProvider, right? Otherwise that makes the name of the container a password, which is not my intention.

It's the name of a container, not of a generator.
If you want different keys each time, just create a new CryptoServiceProvider w/o referencing a container( == stored key-pair).

Following code will delete the key(if exist) related with the containername.
After you delete the key; you can create a new one with the same conatiner name and you will get new random key.
CspParameters cspParams = new CspParameters();
// Specify the container name using the passed variable.
cspParams.KeyContainerName = ContainerName;
//Create a new instance of RSACryptoServiceProvider.
//Pass the CspParameters class to use the
//key in the container.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspParams);
//Delete the key entry in the container.
rsa.PersistKeyInCsp = false;
//Call Clear to release resources and delete the key from the container.
rsa.Clear();

Related

Application specific isolated CngKey

I am creating a CngKey. The key is being generated successfully. The problem I am facing is that, I want to use concept of Sandboxing. Like in Android or iOS, one application cannot access the data of another application. I want the similar behavior in windows application. Here is my code to create a CngKey:
private CngKey generateKeyPair(string keyId)
{
CngKey key = null;
if (CngKey.Exists(keyId, CngProvider.MicrosoftSoftwareKeyStorageProvider, CngKeyOpenOptions.MachineKey))
{
key = CngKey.Open(keyId, CngProvider.MicrosoftSoftwareKeyStorageProvider, CngKeyOpenOptions.MachineKey);
}
else
{
key = CngKey.Create(CngAlgorithm.ECDsaP256, keyId, new CngKeyCreationParameters
{
ExportPolicy = CngExportPolicies.None,
KeyCreationOptions = CngKeyCreationOptions.MachineKey,
KeyUsage = CngKeyUsages.AllUsages,
Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
UIPolicy = new CngUIPolicy(CngUIProtectionLevels.None)
});
}
return key
}
Let's say this key is created by Application_A. But if Application_B knows the keyId, it can access the same key. I don't want this. I cannot use GenericRead access rule to restrict it:
new CryptoKeyAccessRule(
new SecurityIdentifier(WellKnownSidType.NetworkServiceSid, null),
CryptoKeyRights.GenericRead,
AccessControlType.Allow)
Since I have to access it at various points in Application_A.
How can I achieve my goal? Am I using the right thing for my purpose?

How do I ensure that RSACryptoServiceProvider always creates a new key pair?

This answer nicely explains how to create a new public/private key pair in .NET:
public static void AssignNewKey(){
RSA rsa = new RSACryptoServiceProvider(2048); // Generate a new 2048 bit RSA key
string publicPrivateKeyXML = rsa.ToXmlString(true);
string publicOnlyKeyXML = rsa.ToXmlString(false);
// do stuff with keys...
}
However, the "Remarks" section in the documentation of RSACryptoServiceProvider(Int32) says (emphasis mine):
Remarks
If no default key is found, a new key is created. [...]
What is this "default key" and how can I make sure that the RSACryptoServiceProvider constructor does not return that "default key" but a new key?

SignedXml Compute Signature with SHA256

I am trying to digitally sign a XML document using SHA256.
I am trying to use Security.Cryptography.dll for this.
Here is my code -
CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription),"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
X509Certificate2 cert = new X509Certificate2(#"location of pks file", "password");
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load(#"input.xml");
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.PrivateKey;
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
//
// Add a signing reference, the uri is empty and so the whole document
// is signed.
Reference reference = new Reference();
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.Uri = "";
signedXml.AddReference(reference);
//
// Add the certificate as key info, because of this the certificate
// with the public key will be added in the signature part.
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
// Generate the signature.
signedXml.ComputeSignature();
But i am getting "Invalid algorithm specified." error at signedXml.ComputeSignature();. Can anyone tell me what I am doing wrong?
X509Certificate2 loads the private key from the pfx file into the Microsoft Enhanced Cryptographic Provider v1.0 (provider type 1 a.k.a. PROV_RSA_FULL) which doesn't support SHA-256.
The CNG-based cryptographic providers (introduced in Vista and Server 2008) support more algorithms than the CryptoAPI-based providers, but the .NET code still seems to be working with CryptoAPI-based classes like RSACryptoServiceProvider rather than RSACng so we have to work around these limitations.
However, another CryptoAPI provider, Microsoft Enhanced RSA and AES Cryptographic Provider (provider type 24 a.k.a. PROV_RSA_AES) does support SHA-256. So if we get the private key into this provider, we can sign with it.
First, you'll have to adjust your X509Certificate2 constructor to enable the key to be exported out of the provider that X509Certificate2 puts it into by adding the X509KeyStorageFlags.Exportable flag:
X509Certificate2 cert = new X509Certificate2(
#"location of pks file", "password",
X509KeyStorageFlags.Exportable);
And export the private key:
var exportedKeyMaterial = cert.PrivateKey.ToXmlString(
/* includePrivateParameters = */ true);
Then create a new RSACryptoServiceProvider instance for a provider that supports SHA-256:
var key = new RSACryptoServiceProvider(
new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
And import the private key into it:
key.FromXmlString(exportedKeyMaterial);
When you've created your SignedXml instance, tell it to use key rather than cert.PrivateKey:
signedXml.SigningKey = key;
And it will now work.
Here are the list of provider types and their codes on MSDN.
Here's the full adjusted code for your example:
CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
X509Certificate2 cert = new X509Certificate2(#"location of pks file", "password", X509KeyStorageFlags.Exportable);
// Export private key from cert.PrivateKey and import into a PROV_RSA_AES provider:
var exportedKeyMaterial = cert.PrivateKey.ToXmlString( /* includePrivateParameters = */ true);
var key = new RSACryptoServiceProvider(new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
key.FromXmlString(exportedKeyMaterial);
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load(#"input.xml");
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = key;
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
//
// Add a signing reference, the uri is empty and so the whole document
// is signed.
Reference reference = new Reference();
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.Uri = "";
signedXml.AddReference(reference);
//
// Add the certificate as key info, because of this the certificate
// with the public key will be added in the signature part.
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
// Generate the signature.
signedXml.ComputeSignature();
Exporting and re-importing has already been given as an answer, but there are a couple other options that you should be aware of.
1. Use GetRSAPrivateKey and .NET 4.6.2 (currently in preview)
The GetRSAPrivateKey (extension) method returns an RSA instance of "the best available type" for the key and platform (as opposed to the PrivateKey property which "everyone knows" returns RSACryptoServiceProvider).
In 99.99(etc)% of all RSA private keys the returned object from this method is capable of doing SHA-2 signature generation.
While that method was added in .NET 4.6(.0) the requirement of 4.6.2 exists in this case because the RSA instance returned from GetRSAPrivateKey didn't work with SignedXml. That has since been fixed (162556).
2. Re-open the key without export
I, personally, don't like this approach because it uses the (now-legacy) PrivateKey property and RSACryptoServiceProvider class. But, it has the advantage of working on all versions of .NET Framework (though not .NET Core on non-Windows systems, since RSACryptoServiceProvider is Windows-only).
private static RSACryptoServiceProvider UpgradeCsp(RSACryptoServiceProvider currentKey)
{
const int PROV_RSA_AES = 24;
CspKeyContainerInfo info = currentKey.CspKeyContainerInfo;
// WARNING: 3rd party providers and smart card providers may not handle this upgrade.
// You may wish to test that the info.ProviderName value is a known-convertible value.
CspParameters cspParameters = new CspParameters(PROV_RSA_AES)
{
KeyContainerName = info.KeyContainerName,
KeyNumber = (int)info.KeyNumber,
Flags = CspProviderFlags.UseExistingKey,
};
if (info.MachineKeyStore)
{
cspParameters.Flags |= CspProviderFlags.UseMachineKeyStore;
}
if (info.ProviderType == PROV_RSA_AES)
{
// Already a PROV_RSA_AES, copy the ProviderName in case it's 3rd party
cspParameters.ProviderName = info.ProviderName;
}
return new RSACryptoServiceProvider(cspParameters);
}
If you already have cert.PrivateKey cast as an RSACryptoServiceProvider you can send it through UpgradeCsp. Since this is opening an existing key there'll be no extra material written to disk, it uses the same permissions as the existing key, and it does not require you to do an export.
But (BEWARE!) do NOT set PersistKeyInCsp=false, because that will erase the original key when the clone is closed.
If you run into this issue after upgrading to .Net 4.7.1 or above:
.Net 4.7 and below:
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.PrivateKey;
.Net 4.7.1 and above:
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.GetRSAPrivateKey();
Credits to Vladimir Kocjancic
In dotnet core I had this:
var xml = new SignedXml(request) {SigningKey = privateKey};
xml.SignedInfo.CanonicalizationMethod =
SignedXml.XmlDsigExcC14NTransformUrl;
xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigSHA256Url;
xml.KeyInfo = keyInfo;
xml.AddReference(reference);
xml.ComputeSignature();
which did not work. Instead i used this
var xml = new SignedXml(request) {SigningKey = privateKey};
xml.SignedInfo.CanonicalizationMethod =
SignedXml.XmlDsigExcC14NTransformUrl;
xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url;
xml.KeyInfo = keyInfo;
xml.AddReference(reference);
xml.ComputeSignature();
changed signature method => xml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url

How to store a public key in a machine-level RSA key container

I'm having a problem using a machine level RSA key container when storing only the public key of a public/private key pair.
The following code creates a public/private pair and extracts the public key from that pair. The pair and the public key are stored in separate key containers. The keys are then obtained from those key containers at which point they should be the same as the keys going into the containers.
The code works when CspProviderFlags.UseDefaultKeyContainer is specified for CspParameters.Flags (i.e. the key read back out from the PublicKey container is the same), but when CspProviderFlags.UseMachineKeyStore is specified for CspParameters.Flags the key read back from PublicKey is different.
Why is the behaviour different, and what do I need to do differently to retrieve the public key from a machine-level RSA key container?
var publicPrivateRsa = new RSACryptoServiceProvider(new CspParameters()
{
KeyContainerName = "PublicPrivateKey",
Flags = CspProviderFlags.UseMachineKeyStore
//Flags = CspProviderFlags.UseDefaultKeyContainer
}
)
{
PersistKeyInCsp = true,
};
var publicRsa = new RSACryptoServiceProvider(new CspParameters()
{
KeyContainerName = "PublicKey",
Flags = CspProviderFlags.UseMachineKeyStore
//Flags = CspProviderFlags.UseDefaultKeyContainer
}
)
{
PersistKeyInCsp = true
};
//Export the key.
publicRsa.ImportParameters(publicPrivateRsa.ExportParameters(false));
Console.WriteLine(publicRsa.ToXmlString(false));
Console.WriteLine(publicPrivateRsa.ToXmlString(false));
//Dispose those two CSPs.
using (publicRsa)
{
publicRsa.Clear();
}
using (publicPrivateRsa)
{
publicRsa.Clear();
}
publicPrivateRsa = new RSACryptoServiceProvider(new CspParameters()
{
KeyContainerName = "PublicPrivateKey",
Flags = CspProviderFlags.UseMachineKeyStore
//Flags = CspProviderFlags.UseDefaultKeyContainer
}
);
publicRsa = new RSACryptoServiceProvider(new CspParameters()
{
KeyContainerName = "PublicKey",
Flags = CspProviderFlags.UseMachineKeyStore
//Flags = CspProviderFlags.UseDefaultKeyContainer
}
);
Console.WriteLine(publicRsa.ToXmlString(false));
Console.WriteLine(publicPrivateRsa.ToXmlString(false));
using (publicRsa)
{
publicRsa.Clear();
}
using (publicPrivateRsa)
{
publicRsa.Clear();
}
It seems that key containers are not intended for this purpose (this is implied by "How to: Store Asymmetric Keys in a Key Container" from the .NET Framework Developer's Guide, and confirmed by a disccusion on MSDN).
Other mechanisms, such as storing the key in an XML file, need to be used to achieve this goal.
This link may help you. http://msdn.microsoft.com/en-IN/library/tswxhw92(v=vs.80).aspx
var cp = new CspParameters();
cp.KeyContainerName = ContainerName;
// Create a new instance of RSACryptoServiceProvider that accesses
// the key container.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp);
// Delete the key entry in the container.
rsa.PersistKeyInCsp = false;
// Call Clear to release resources and delete the key from the container.
rsa.Clear();
This is what it says about key deletion.

Can someone explain C# CngKey.Create please?

The internet resources seem few and far between and the best MSDN page (as far as I could tell) throws an error!
Specifically, I'm not sure what to create as a CngKeyCreationParameters object...
CngKey : CngKey objects contain properties.
Some properties must be added to a key when it is created. Other properties
can be added after the key is created.
CngKeyCreationParameters:
The CngKeyCreationParameters class enables you to add properties to a key as it is being created.
your problem: I'm not sure what to create as a CngKeyCreationParameters object
here is how to do do this
// Create CngKeyCreationParameters
CngKeyCreationParameters keyParams = new CngKeyCreationParameters();
// set properties accordingly
keyParams.ExportPolicy = CngExportPolicies.AllowArchiving;
keyParams.KeyCreationOptions = CngKeyCreationOptions.MachineKey;
keyParams.Provider = new CngProvider("someprovider");
// here is how to use keyParams
CngKey mycngKey =
CngKey.Create(new CngAlgorithm(""), "keyName", keyParams);

Categories

Resources