SignedXml Compute Signature with SHA256 - c#

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

Related

Walmart Affiliate Signature C# .Net

I am trying to create a sha256 signature using a RSA Private Key but I am getting a 401 "Could not authenticate in-request, auth signature : Signature verification failed: affil-product, version: 2.0.0, env: prod
I think the issue is to do whit how it get my .pem file. I have read the Microsoft documentation and the provided Walmart example. I am following this guide. I created a non password protected key pair and uploaded the public key to Walmart. I then added my consumer ID and key version to appsettings.json {"Settings": {"consumerID": "e2ca6a2f-56f2-4465-88b3-273573b1e0c9","keyVer": "4"}}.
I am then getting this data in program.cs via the following code.
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
// Get values from the config given their key and their target type.
Settings settings = config.GetRequiredSection("Settings").Get<Settings>();
I then instantiate Walmart affiliate object allowing us to use the methods needed to access and read Walmart api
WalMartAfilAPI wallMartAfilAPI = new WalMartAfilAPI();
From there I Create a RSACryptoServiceProvider object and import the .pem and export the parameter.
RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
var rsaPem = File.ReadAllText("D:\\Users\\Adam\\source\\repos\\DealsBot\\DealsBot\\DealsBot\\wallmartAfill\\WM_IO_private_key.pem");
//now we instantiate the RSA object
var rsa = RSA.Create();
//replace the private key with our .pem
rsa.ImportFromPem(rsaPem);
//Export the key information to an RSAParameters object.
// You must pass true to export the private key for signing.
// However, you do not need to export the private key
// for verification.
RSAParameters Key = rsa.ExportParameters(true);
From here I get the time stamp and call methods from the Walmart Affiliate object.
//Get current im in unix epoch milliseconds
TimeSpan t = DateTime.UtcNow - new DateTime(1970, 1, 1);
var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
Console.WriteLine(time);
byte[] conicData = wallMartAfilAPI.Canonicalize(settings.KeyVer, settings.ConsumerID, time);
byte[] signedData = wallMartAfilAPI.HashAndSignBytes(conicData, Key);
if (wallMartAfilAPI.VerifySignedHash(conicData, signedData, Key))
{
Console.WriteLine("The data was verified");
;
Console.WriteLine(Convert.ToBase64String(signedData));
}
else
{
Here is the WalMartAfilAPI class
namespace DealsBot.wallmartAfill
{
public class WalMartAfilAPI
{
public byte[] Canonicalize(string version, string consumerId, string timestamp)
{
ASCIIEncoding ByteConverter = new ASCIIEncoding();
// Follow after the java code, which just orders the keys/values.
StringBuilder keyBuilder = new StringBuilder();
StringBuilder valueBuilder = new StringBuilder();
SortedDictionary<string, string> dictionary = new SortedDictionary<string, string>() { { "WM_CONSUMER.ID", consumerId }, { "WM_CONSUMER.INTIMESTAMP", timestamp }, { "WM_SEC.KEY_VERSION", version } };
foreach (string key in dictionary.Keys)
{
keyBuilder.Append($"{key.Trim()};");
valueBuilder.AppendLine($"{dictionary[key].Trim()}");
}
string[] conHeader = { keyBuilder.ToString(), valueBuilder.ToString() };
byte[] originalData = ByteConverter.GetBytes(conHeader[1]);
return originalData;
}
public byte[] HashAndSignBytes(byte[] DataToSign, RSAParameters Key)
{
try
{
// Create a new instance of RSACryptoServiceProvider using the
// key from RSAParameters.
RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
RSAalg.ImportParameters(Key);
// Hash and sign the data. Pass a new instance of SHA256
// to specify the hashing algorithm.
return RSAalg.SignData(DataToSign, SHA256.Create());
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}
public bool VerifySignedHash(byte[] DataToVerify, byte[] SignedData, RSAParameters Key)
{
try
{
// Create a new instance of RSACryptoServiceProvider using the
// key from RSAParameters.
RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
RSAalg.ImportParameters(Key);
// Verify the data using the signature. Pass a new instance of SHA256
// to specify the hashing algorithm.
return RSAalg.VerifyData(DataToVerify, SHA256.Create(), SignedData);
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return false;
}
}
}
}
As of today, auth signature code is available in Java (https://www.walmart.io/docs/affiliate/onboarding-guide)
The idea we provided sample code to help the customers to implement the logic at customer end by referring it
You can implement the logic in(C# .NET, Python, PHP or JS) in such a way that whenever your system invoking Walmart APIs, generate the signature on the fly and pass as input parameter
This is how all of customers implemented and consuming our APIs
Pls refer the below documentation for complete.
https://walmart.io/docs/affiliate/quick-start-guide
https://www.walmart.io/docs/affiliate/onboarding-guide
Regards,
Firdos
IO Support

Generating assertion signature with private key and certificate?

I've been give a private key, public key and a certificate to try and generate a signature for an SSO application, I've been struggling with this for a while now and I've think I've finally managed to get some code close to working as needed. I made a post a while back here: iDP connecting to SP SAML / SSO which has helped me get in the right direction.
However I am still unsure on how I meant to be signing this signature, all the code I seem to come by says I need to use the .Net class X509Certificate which usually tried to load in another file, however the certificate is in the assertion file itself.
<ds:X509Data>
<ds:X509Certificate>X509Certificate Goes Here</ds:X509Certificate>
</ds:X509Data>
I have this method here:
private SignedXml SignSignature(XmlDocument assertionDoc, string uri, string digest)
{
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);
SignedXml signedXml = new SignedXml(assertionDoc);
Reference reference = new Reference();
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url;
reference.Uri = uri;
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.DigestMethod = SignedXml.XmlDsigSHA1Url;
reference.DigestValue = Encoding.ASCII.GetBytes(digest);
signedXml.AddReference(reference);
signedXml.SigningKey = rsaKey;
HMACSHA256 key = new HMACSHA256(Encoding.ASCII.GetBytes(PrivateKey));
signedXml.ComputeSignature(key);
return signedXml;
}
Which is what I am using to sign SignatureValue of the document, however I am only making use of the private key in the SHA256 class and not the certificate, I'm not even sure if I am using the private key correctly, overall I think I am making this more complicated than it needs to be and hopefully someone from here can assist me.
When you sign an assertion, you only need to use the private key of a certificate which is the case of your code.
However, usually the public key of the certificate is inserted as keyinfo into the signature to notify a receiver. This ends up as the ds:X509Data section you mentioned above. In order to do that, you need to add some more lines of code into the method above. You can find sample code at: https://github.com/Safewhere/CHTestSigningService/blob/86a66950d1ffa5208b8bf80d03868a073ba29f12/Kombit.Samples.CHTestSigningService/Code/TokenSigningService.cs#L344 (notice line 361, 362, and 368).

Using an X509 private key to sign data in dotnet core v2 (SHA256)

I'm having trouble reproducing some cryptographic functionality in dotnet core v2.0. This is code ported from a .NET 4.5 project
.NET 4.5 code
public byte[] SignData(byte[] dataToSign, X509Certificate2 certificate)
{
var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
var xml = certificate.PrivateKey.ToXmlString(true);
rsaCryptoServiceProvider.FromXmlString(xml);
var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, CryptoConfig.MapNameToOID("SHA256"));
return signedBytes;
}
In dotnet core the ToXmlString() and FromXmlString() methods are not implemented, so I used a helper class workaround. Aside from that the dotnet core implementation works but, given the same input data and certificate it produces a different outcome.
dotnet core v2.0 code
public byte[] SignData(byte[] dataToSign, X509Certificate2 certificate)
{
var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
var rsa = (RSA)certificate.PrivateKey;
var xml = RSAHelper.ToXmlString(rsa);
var parameters = RSAHelper.GetParametersFromXmlString(rsa, xml);
rsaCryptoServiceProvider.ImportParameters(parameters);
SHA256 alg = SHA256.Create();
var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, alg);
return signedBytes;
}
EDIT
The dotnet core signed data fails a signature verification check in the .NET 4.5 codebase. Theoretically it should make no difference what the signing method was, so this should work but doesn't.
public void VerifySignature(byte[] signedData, byte[] unsignedData, X509Certificate2 certificate)
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PublicKey.Key)
{
if (rsa.VerifyData(unsignedData, CryptoConfig.MapNameToOID("SHA256"), signedData))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
}
Does anyone know if there are compatibility issues between the two methods of signing data?
EDIT 2
For clarification this is what both code snippets are attempting:
Taking a X509 certificate's private key which is RSA-FULL and cannot sign using SHA256 encoding
Creating a new private key which is RSA-AES which can sign using SHA256 encoding
Import your X509 private key into this new private key
Signing the required data using this private key.
The complication comes when attempting the same thing in .NEt4.5 and dotnet core v2.0.
Seems there are differences between frameworks, libraries and OS's. This answer states that the RSACryptoServiceProvider object relies on the CryptoAPI of the machine the software is on in .NET 4.5, and this informative post shows you the difference on how this is implemented in different environments/frameworks.
I'm still working on a solution based on this information but am left the central issue, namely that signed data using this dotnet core above cannot be verified by the .NET 4.5 implementation.
If you MUST stick with 4.5, your .NET Framework code is as good as it gets. (Well, you could eliminate the usage of the XML format and just use ExportParameters directly)
In .NET 4.6 the problem was solved with the soft-deprecation (which just means I tell everyone on StackOverflow to not use it) of the PrivateKey property:
using (RSA rsa = certificate.GetRSAPrivateKey())
{
return rsa.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
This is the same code you should write for .NET Core (all versions). Part of the reason for the refactoring was to get people off of the RSACryptoServiceProvider type, which doesn't work well on non-Windows systems.
The verification code would be
using (RSA rsa = certificate.GetRSAPublicKey())
{
return rsa.VerifyData(
dataToSign,
signature,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
Much less code, stronger type-safety, doesn't have the PROV_RSA_FULL problem, no key exporting/importing...
Solution
To be able to verify in .NET 4.5 the data signed using a X509 RSA private key in dotnet core v2.0
Verification code (.NET 4.5)
public void VerifySignedData(byte[] originalData, byte[] signedData, X509Certificate2 certificate)
{
using (var rsa = (RSACryptoServiceProvider)certificate.PublicKey.Key)
{
if (rsa.VerifyData(originalData, CryptoConfig.MapNameToOID("SHA256"), signedData))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
}
Signing Code (dotnet core v2.0)
private byte[] SignData(X509Certificate2 certificate, byte[] dataToSign)
{
// get xml params from current private key
var rsa = (RSA)certificate.PrivateKey;
var xml = RSAHelper.ToXmlString(rsa, true);
var parameters = RSAHelper.GetParametersFromXmlString(rsa, xml);
// generate new private key in correct format
var cspParams = new CspParameters()
{
ProviderType = 24,
ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
};
var rsaCryptoServiceProvider = new RSACryptoServiceProvider(cspParams);
rsaCryptoServiceProvider.ImportParameters(parameters);
// sign data
var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return signedBytes;
}
Helper Class
public static class RSAHelper
{
public static RSAParameters GetParametersFromXmlString(RSA rsa, string xmlString)
{
RSAParameters parameters = new RSAParameters();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlString);
if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
{
foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
{
switch (node.Name)
{
case "Modulus": parameters.Modulus = Convert.FromBase64String(node.InnerText); break;
case "Exponent": parameters.Exponent = Convert.FromBase64String(node.InnerText); break;
case "P": parameters.P = Convert.FromBase64String(node.InnerText); break;
case "Q": parameters.Q = Convert.FromBase64String(node.InnerText); break;
case "DP": parameters.DP = Convert.FromBase64String(node.InnerText); break;
case "DQ": parameters.DQ = Convert.FromBase64String(node.InnerText); break;
case "InverseQ": parameters.InverseQ = Convert.FromBase64String(node.InnerText); break;
case "D": parameters.D = Convert.FromBase64String(node.InnerText); break;
}
}
}
else
{
throw new Exception("Invalid XML RSA key.");
}
return parameters;
}
public static string ToXmlString(RSA rsa, bool includePrivateParameters)
{
RSAParameters parameters = rsa.ExportParameters(includePrivateParameters);
return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
Convert.ToBase64String(parameters.Modulus),
Convert.ToBase64String(parameters.Exponent),
Convert.ToBase64String(parameters.P),
Convert.ToBase64String(parameters.Q),
Convert.ToBase64String(parameters.DP),
Convert.ToBase64String(parameters.DQ),
Convert.ToBase64String(parameters.InverseQ),
Convert.ToBase64String(parameters.D));
}
}

RSACryptoProvider generates same key repeatedly for same CspParameter ContainerName

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();

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.

Categories

Resources