SignedXml Compute Signature with SHA256

th1rdey3 picture th1rdey3 · Mar 12, 2015 · Viewed 18.2k times · Source

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?

Answer

softwariness picture softwariness · Mar 14, 2015

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