J'essaie de signer numériquement un document XML à l'aide de SHA256.
J'essaie d'utiliser Security.Cryptography.dll pour cela.
Voici mon 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();
Mais je reçois "Algorithme non valide spécifié." erreur à signedXml.ComputeSignature();
. Quelqu'un peut-il me dire ce que je fais mal?
X509Certificate2
charge la clé privée du fichier pfx dans Microsoft Enhanced Cryptographic Provider v1.0 (type de fournisseur 1
alias. PROV_RSA_FULL
) qui ne prend pas en charge SHA-256.
Les fournisseurs de chiffrement basés sur CNG (introduits dans Vista et Server 2008) prennent en charge plus d'algorithmes que les fournisseurs basés sur CryptoAPI, mais le code .NET semble toujours fonctionner avec des classes basées sur CryptoAPI comme RSACryptoServiceProvider
plutôt que RSACng
nous devons donc contourner ces limitations.
Cependant, un autre fournisseur CryptoAPI, Microsoft Enhanced RSA and AES Cryptographic Provider (provider type 24
alias. PROV_RSA_AES
) prend en charge SHA-256. Donc, si nous obtenons la clé privée dans ce fournisseur, nous pouvons signer avec.
Tout d'abord, vous devrez ajuster votre X509Certificate2
constructeur permettant d'exporter la clé hors du fournisseur X509Certificate2
le met en ajoutant le X509KeyStorageFlags.Exportable
drapeau:
X509Certificate2 cert = new X509Certificate2(
@"location of pks file", "password",
X509KeyStorageFlags.Exportable);
Et exportez la clé privée:
var exportedKeyMaterial = cert.PrivateKey.ToXmlString(
/* includePrivateParameters = */ true);
Créez ensuite une nouvelle instance de RSACryptoServiceProvider
pour un fournisseur qui prend en charge SHA-256:
var key = new RSACryptoServiceProvider(
new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
Et importez-y la clé privée:
key.FromXmlString(exportedKeyMaterial);
Lorsque vous avez créé votre instance SignedXml
, dites-lui d'utiliser key
plutôt que cert.PrivateKey
:
signedXml.SigningKey = key;
Et ça va maintenant marcher.
Voici la liste des types de fournisseurs et leurs codes sur MSDN.
Voici le code ajusté complet pour votre exemple:
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();
L'exportation et la réimportation a déjà a été donnée comme réponse , mais il y a quelques autres options que vous devez connaître.
La méthode GetRSAPrivateKey (extension) renvoie une instance RSA du "meilleur type disponible" pour la clé et la plate-forme (par opposition à la propriété PrivateKey que "tout le monde connaît" renvoie RSACryptoServiceProvider).
Dans 99,99 (etc)% de toutes les clés privées RSA, l'objet renvoyé par cette méthode est capable de générer une signature SHA-2.
Bien que cette méthode ait été ajoutée dans .NET 4.6 (.0), l'exigence de 4.6.2 existe dans ce cas, car l'instance RSA renvoyée par GetRSAPrivateKey ne fonctionnait pas avec SignedXml. Cela a depuis corrigé (162556).
Personnellement, je n'aime pas cette approche car elle utilise la propriété PrivateKey (désormais héritée) et la classe RSACryptoServiceProvider. Mais, il a l'avantage de fonctionner sur toutes les versions de .NET Framework (mais pas sur .NET Core sur les systèmes non Windows, car RSACryptoServiceProvider est uniquement 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 vous disposez déjà de la certification cert.PrivateKey en tant que fournisseur RSACryptoServiceProvider, vous pouvez l'envoyer via UpgradeCsp. Puisqu'il s'agit d'ouvrir une clé existante, il n'y aura pas de matériel supplémentaire écrit sur le disque, il utilise les mêmes autorisations que la clé existante et ne vous oblige pas à effectuer une exportation.
Mais (ATTENTION!) NE définissez PAS PersistKeyInCsp = false, car cela effacera la clé d'origine lorsque le clone sera fermé.