Подтвердить что ты не робот

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();. Может ли кто-нибудь сказать мне, что я делаю неправильно?

4b9b3361

Ответ 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, потому что это приведет к стиранию исходного ключа, когда клон будет закрыт.