La firma SHA256 deja de funcionar en.NET 4.5
certificate wif (3)
A pesar de que esta pregunta es bastante antigua, estoy bastante seguro de que alguien está obligado a encontrarla tarde o temprano.
Últimamente, nos hemos ocupado de un servicio que se ejecuta en un servidor que solo admite TLS 1.2 y tiene el algoritmo de hash SHA-1 desactivado. Necesitamos firmar todo el archivo pain.something.something, que hace que la respuesta más popular sea inútil.
Esto es lo que descubrimos:
- Si usa la clave privada del certificado como SigningKey, solo puede usar el algoritmo SHA-1 para las firmas.
- Necesita obtener una clave privada utilizando el método
GetRSAPrivateKey
. - Es necesario establecer SignatureMethod de signedXML en
SecurityAlgorithms.RsaSha256Signature
- Debe establecer DigestMethod del objeto de referencia en
SecurityAlgorithms.Sha256Digest
.
Entonces, ya puedes irte. Aquí también hay un código de ejemplo que hace esto:
private static void SignXmlDocumentEx(XmlElement el, X509Certificate2 cert)
{
var dataId = string.Format("Signature-{0}", Guid.NewGuid());
var signedXml = new System.Security.Cryptography.Xml.SignedXml(el);
signedXml.SigningKey = cert.GetRSAPrivateKey();
signedXml.SignedInfo.SignatureMethod = SecurityAlgorithms.RsaSha256Signature;
signedXml.Signature.Id = dataId;
var reference = new Reference(dataId);
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.Uri = "";
reference.DigestMethod = SecurityAlgorithms.Sha256Digest;
signedXml.AddReference(reference);
signedXml.KeyInfo = new KeyInfo();
signedXml.KeyInfo.AddClause(new KeyInfoX509Data(cert, X509IncludeOption.EndCertOnly));
signedXml.ComputeSignature();
el.AppendChild(signedXml.GetXml());
}
Tenemos un fragmento de código que crea un objeto SigningCredentials para usarlo para firmar un documento XML utilizando el algoritmo SHA256. Funciona con .NET 3.5 perfectamente. Sin embargo, cuando actualizamos nuestro código base a .NET 4.5, deja de funcionar. ¡El mismo código, el mismo certificado! He pasado horas en la depuración y la búsqueda en Internet sin suerte.
¿Podría alguien decirme cuál es el problema aquí? Gracias de antemano.
Código para crear SigningCredentials:
public SigningCredentials CreateSigningCredentials(X509Certificate2 cert)
{
var ski = new SecurityKeyIdentifier(new X509RawDataKeyIdentifierClause(cert));
return new SigningCredentials(new X509AsymmetricSecurityKey(cert), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "http://www.w3.org/2001/04/xmlenc#sha256", ski);
}
Excepción:
[CryptographicException: Invalid algorithm specified.
]
System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr) +41
System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash, Int32 cbHash, ObjectHandleOnStack retSignature) +0
System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash) +118
System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, Int32 calgHash) +334
System.Security.Cryptography.RSAPKCS1SignatureFormatter.CreateSignature(Byte[] rgbHash) +321
System.IdentityModel.SignedXml.ComputeSignature(HashAlgorithm hash, AsymmetricSignatureFormatter formatter, String signatureMethod) +323
System.IdentityModel.SignedXml.ComputeSignature(SecurityKey signingKey) +690
System.IdentityModel.EnvelopedSignatureWriter.ComputeSignature() +338
System.IdentityModel.EnvelopedSignatureWriter.OnEndRootElement() +278
System.IdentityModel.Metadata.MetadataSerializer.WriteEntityDescriptor(XmlWriter inputWriter, EntityDescriptor entityDescriptor) +1109
Si bien esta pregunta se hizo hace casi un año, recientemente recibió algunos votos, lo que puede indicar que otras personas tienen el mismo problema. Esperemos que esta respuesta pueda ayudar :) En pocas palabras, el error no ocurre en todas las máquinas, sino en algunas de ellas. Supongo que depende de qué CSP se hayan registrado en una máquina específica. De todos modos, en mi caso específico, el certificado se generó con "Microsoft RSA SChannel ..." o "Proveedor fuerte de criptografía de Microsoft" como el CSP. Generé un nuevo certificado, pero utilicé "Microsoft Enhanced RSA y AES Cryptographic Provider" como el CSP y la firma SHA256 comenzó a funcionar para mí.
Algunas referencias:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/e391ba75-ce6e-431c-bfc9-26a71ae1b033/sha256-signing-stops-working-in-net-45?forum=Geneva (como usted) puedo ver, millones gracias a Paul que me ayudó a resolver este problema)
Tuvo el mismo problema con XmlDsig (al intentar hacer una firma envolvente de un documento XML con el algoritmo RSA-SHA256). Primero estaba recibiendo la excepción
System.Security.Cryptography.CryptographicException: No se pudo crear SignatureDescription para el algoritmo de firma proporcionado.
Luego encontré la mención de RSAPKCS1SHA256SignatureDescription
: implementación de la descripción de la firma para las firmas RSA-SHA256.
Implementación completa aquí:
http://clrsecurity.codeplex.com/SourceControl/changeset/view/47833#269110
o aquí: https://gist.github.com/sneal/f35de432115b840c4c1f
Tienes que llamar manualmente una vez por appdomain:
CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
Después de eso obtuve una nueva excepción:
System.Security.Cryptography.CryptographicException: Se especificó un algoritmo no válido.
Esto me trajo a tu pregunta. Después de leer el article sugerido, hice una nueva clave y certificado (con OpenSSL) utilizando Microsoft Enhanced RSA and AES Cryptographic Provider
.
Para mi sorpresa, este nuevo certificado me permitió hacer una firma con éxito.
Después de un poco más de investigación, encontré una interesante respuesta de Andrew aquí https://.com/a/17285774/328785 , donde usó RSACryptoServiceProvider
para preparar SecretKey para la clase SignedXml
. Específicamente esta parte (mi interpretación):
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySerialNumber, "54dba096", true);
var certificate = certificates[0];
// next three lines
var cspParams = new CspParameters(24) { KeyContainerName = "XML_DSIG_RSA_KEY" };
var key = new RSACryptoServiceProvider(cspParams);
key.FromXmlString(certificate.PrivateKey.ToXmlString(true));
SignedXml sxml = new SignedXml(doc);
sxml.SigningKey = key;
¡Y estas soluciones funcionaron bien incluso con la llave antigua!