validate - xml signature c#
SignedXml Compute Signature con SHA256 (2)
Estoy intentando firmar digitalmente un documento XML usando SHA256.
Estoy tratando de usar Security.Cryptography.dll para esto.
Aquí está mi código -
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();
Pero estoy recibiendo "Algoritmo no válido especificado". error en signedXml.ComputeSignature();
. ¿Alguien puede decirme que estoy haciendo mal?
La exportación y la reimportación ya se han dado como respuesta , pero hay algunas otras opciones que debe tener en cuenta.
1. Use GetRSAPrivateKey y .NET 4.6.2 (actualmente en vista previa)
El GetRSAPrivateKey (extensión) devuelve una instancia RSA del "mejor tipo disponible" para la clave y la plataforma (a diferencia de la propiedad PrivateKey que "todos saben" devuelve RSACryptoServiceProvider).
En el 99,99 (etc.)% de todas las claves privadas RSA, el objeto devuelto por este método es capaz de realizar la generación de 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 RSA devuelta desde GetRSAPrivateKey no funcionó con SignedXml. Eso ha sido arreglado desde entonces (162556).
2. Vuelva a abrir la llave sin exportar.
A mí, personalmente, no me gusta este enfoque porque utiliza la propiedad PrivateKey (ahora legada) y la clase RSACryptoServiceProvider. Pero tiene la ventaja de trabajar en todas las versiones de .NET Framework (aunque no .NET Core en sistemas que no son 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)
{
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);
}
Si ya tiene cert.PrivateKey emitido como RSACryptoServiceProvider, puede enviarlo a través de UpgradeCsp. Ya que esto abre una clave existente, no habrá material adicional escrito en el disco, utiliza los mismos permisos que la clave existente y no requiere que haga una exportación.
Pero (¡CUIDADO!) NO establezca PersistKeyInCsp = false, porque eso borrará la clave original cuando se cierre el clon.
X509Certificate2
carga la clave privada del archivo pfx en el proveedor de criptografía mejorada de Microsoft v1.0 (tipo de proveedor 1
también PROV_RSA_FULL
como 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 aún parece funcionar con clases basadas en CryptoAPI como RSACryptoServiceProvider
lugar de RSACng
así que tenemos que trabajar alrededor estas limitaciones.
Sin embargo, otro proveedor de CryptoAPI, Microsoft Enhanced RSA y AES Cryptographic Provider (proveedor de tipo 24
también PROV_RSA_AES
como PROV_RSA_AES
) es compatible con SHA-256. Entonces, si ingresamos la clave privada a este proveedor, podemos firmar con ella.
Primero, tendrá que ajustar su constructor X509Certificate2
para permitir que la clave se exporte fuera del proveedor en el que X509Certificate2
la X509KeyStorageFlags.Exportable
agregando el indicador X509KeyStorageFlags.Exportable
:
X509Certificate2 cert = new X509Certificate2(
@"location of pks file", "password",
X509KeyStorageFlags.Exportable);
Y exportar la clave privada:
var exportedKeyMaterial = cert.PrivateKey.ToXmlString(
/* includePrivateParameters = */ true);
Luego cree una nueva instancia de RSACryptoServiceProvider
para un proveedor que admita SHA-256:
var key = new RSACryptoServiceProvider(
new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
E importa la clave privada en ella:
key.FromXmlString(exportedKeyMaterial);
Cuando haya creado su instancia de SignedXml
, dígale que use la key
lugar 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 ajustado completo 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();