SignedXml Compute Signature с SHA256
Я пытаюсь подписать документ в формате XML с помощью SHA256.
Я пытаюсь использовать Security.Cryptography.dll для этого.
Вот мой код -
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();
Но я получаю "Недопустимый алгоритм". ошибка при signedXml.ComputeSignature();
. Может ли кто-нибудь сказать мне, что я делаю неправильно?
Ответы
Ответ 1
X509Certificate2
загружает закрытый ключ из файла pfx в Microsoft Enhanced Cryptographic Provider v1.0 (тип провайдера 1
a.k.a. PROV_RSA_FULL
), который не поддерживает SHA-256.
Поставщики криптографии на основе CNG (представленные в Vista и Server 2008) поддерживают больше алгоритмов, чем поставщики на основе CryptoAPI, но код .NET все еще работает с классами на основе CryptoAPI, такими как RSACryptoServiceProvider
, а не RSACng
, поэтому мы должны обойти эти ограничения.
Однако другой поставщик CryptoAPI, Microsoft Enhanced RSA и AES Cryptographic Provider (тип провайдера 24
a.k.a. PROV_RSA_AES
) поддерживает SHA-256. Поэтому, если мы получим секретный ключ в этом провайдере, мы с ним можем подписаться.
Во-первых, вам нужно настроить конструктор X509Certificate2
, чтобы разрешить экспорт ключа из поставщика, который X509Certificate2
помещает его, добавив флаг X509KeyStorageFlags.Exportable
:
X509Certificate2 cert = new X509Certificate2(
@"location of pks file", "password",
X509KeyStorageFlags.Exportable);
И экспортируйте закрытый ключ:
var exportedKeyMaterial = cert.PrivateKey.ToXmlString(
/* includePrivateParameters = */ true);
Затем создайте новый экземпляр RSACryptoServiceProvider
для поставщика, который поддерживает SHA-256:
var key = new RSACryptoServiceProvider(
new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;
И импортируйте в него закрытый ключ:
key.FromXmlString(exportedKeyMaterial);
Когда вы создали свой экземпляр SignedXml
, скажите ему использовать key
, а не cert.PrivateKey
:
signedXml.SigningKey = key;
И теперь он будет работать.
Ниже приведен список типов поставщиков и их кодов в MSDN.
Здесь полный скорректированный код для вашего примера:
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();
Ответ 2
Экспорт и повторный импорт уже был предоставлен как ответ, но есть пара других параметров, о которых вы должны знать.
1. Используйте GetRSAPrivateKey и .NET 4.6.2 (в настоящее время в предварительном просмотре)
Метод GetRSAPrivateKey (extension) возвращает экземпляр RSA "лучший доступный тип" для ключа и платформы (в отличие от к свойству PrivateKey, которое "все знают" возвращает RSACryptoServiceProvider).
В 99.99 (и т.д.)% всех закрытых ключей RSA возвращаемый объект из этого метода способен генерировать подпись SHA-2.
Хотя этот метод был добавлен в .NET 4.6 (.0), требование 4.6.2 существует в этом случае, потому что экземпляр RSA, возвращенный из GetRSAPrivateKey, не работал с SignedXml. С тех пор был исправлен (162556).
2. Повторно откройте ключ без экспорта
Мне лично не нравится этот подход, потому что он использует свойство privateKey (now-legacy) и класс RSACryptoServiceProvider. Но у него есть преимущество в работе над всеми версиями .NET Framework (хотя и не с .NET Core в системах, отличных от Windows, поскольку RSACryptoServiceProvider только для 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 3rd party
cspParameters.ProviderName = info.ProviderName;
}
return new RSACryptoServiceProvider(cspParameters);
}
Если у вас уже есть cert.PrivateKey в качестве RSACryptoServiceProvider, вы можете отправить его через UpgradeCsp. Поскольку это открывает существующий ключ, на диске не будет лишних материалов, он использует те же разрешения, что и существующий, и не требует экспорта.
Но (BEWARE!) НЕ устанавливайте PersistKeyInCsp = false, потому что это приведет к стиранию исходного ключа, когда клон будет закрыт.