Esta es la respuesta más acertada que te podemos brindar, sin embargo obsérvala pausadamente y analiza si se puede adaptar a tu trabajo.
Solución:
X509Certificate2
carga el privado key del archivo pfx al Proveedor criptográfico mejorado de Microsoft v1.0 (tipo de proveedor 1
alias PROV_RSA_FULL
) que no es compatible con SHA-256.
Los proveedores criptográficos basados en CNG (introducidos en Vista y Server 2008) admiten más algoritmos que los proveedores basados en CryptoAPI, pero el código .NET todavía parece funcionar con clases basadas en CryptoAPI como RSACryptoServiceProvider
en vez de RSACng
así que tenemos que trabajar alrededor de estas limitaciones.
Sin embargo, otro proveedor de CryptoAPI, Proveedor de cifrado RSA y AES mejorado de Microsoft (tipo de proveedor 24
alias PROV_RSA_AES
) admite SHA-256. Así que si obtenemos el privado key en este proveedor, podemos firmar con él.
Primero, tendrá que ajustar su X509Certificate2
constructor para habilitar el key para ser exportado fuera del proveedor que X509Certificate2
lo pone agregando el X509KeyStorageFlags.Exportable
bandera:
X509Certificate2 cert = new X509Certificate2(
@"location of pks file", "password",
X509KeyStorageFlags.Exportable);
Y exportar lo privado. key:
var exportedKeyMaterial = cert.PrivateKey.ToXmlString(
/* includePrivateParameters = */ true);
Luego crea una nueva RSACryptoServiceProvider
instancia para un proveedor que admita SHA-256:
var key = new RSACryptoServiceProvider(
new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
E importar lo privado key en ello:
key.FromXmlString(exportedKeyMaterial);
Cuando hayas creado tu SignedXml
ejemplo, dígale que use key
en vez de cert.PrivateKey
:
signedXml.SigningKey = key;
Y ahora funcionará.
Aquí está la lista de tipos de proveedores y sus códigos en MSDN.
Aquí está el código completo ajustado para su ejemplo:
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();
La exportación y la reimportación ya se han dado como respuesta, pero hay un par de otras opciones que debe tener en cuenta.
1. Use GetRSAPrivateKey y .NET 4.6.2 (actualmente en versión preliminar)
El método GetRSAPrivateKey (extensión) devuelve una instancia de RSA del “mejor tipo disponible” para el key y plataforma (a diferencia de la propiedad PrivateKey que “todo el mundo sabe” devuelve RSACryptoServiceProvider).
En 99.99(etc)% de todos los RSA privados keys el objeto devuelto por este método es capaz de generar firmas SHA-2.
Si bien ese método se agregó en .NET 4.6(.0), el requisito de 4.6.2 existe en este caso porque la instancia de RSA que devolvió GetRSAPrivateKey no funcionó con SignedXml. Eso ya se ha solucionado (162556).
2. Vuelva a abrir la key sin exportar
Personalmente, no me gusta este enfoque porque usa la propiedad PrivateKey (ahora heredada) y la clase RSACryptoServiceProvider. Sin embargo, tiene la ventaja de funcionar en todas las versiones de .NET Framework (aunque no en .NET Core en sistemas que no sean Windows, ya que RSACryptoServiceProvider es solo para Windows).
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)
= 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);
Si ya tiene cert.PrivateKey emitido como RSACryptoServiceProvider, puede enviarlo a través de UpgradeCsp. Ya que esto está abriendo un existente key no habrá material adicional escrito en el disco, usa los mismos permisos que el existente keyy no requiere que hagas una exportación.
Pero (¡CUIDADO!) NO establezca PersistKeyInCsp=falseporque eso borrará el original key cuando el clon está cerrado.
Si se encuentra con este problema después de actualizar a .Net 4.7.1 o superior:
.Net 4.7 y anteriores:
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.PrivateKey;
.Net 4.7.1 y superior:
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = cert.GetRSAPrivateKey();
Créditos para vladimir kocjancic
Sección de Reseñas y Valoraciones
Acuérdate de que puedes agregar una reseña si te ayudó.