web-dev-qa-db-ja.com

SignedXml計算署名とSHA256

SHA256を使用してXMLドキュメントにデジタル署名しようとしています。

これに 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();でエラーが発生しました。誰かが私が間違っていることを教えてもらえますか?

18
th1rdey3

X509Certificate2は、pfxファイルからMicrosoft Enhanced Cryptographic Provider v1.0(プロバイダータイプ1 aka PROV_RSA_FULL)SHA-256をサポートしていません。

CNGベースの暗号化プロバイダー(VistaおよびServer 2008で導入)は、CryptoAPIベースのプロバイダーよりも多くのアルゴリズムをサポートしていますが、.NETコードは、RSACryptoServiceProviderではなくRSACngなどのCryptoAPIベースのクラスで動作しているようなので、回避する必要があります。これらの制限。

ただし、別のCryptoAPIプロバイダーMicrosoft Enhanced RSAおよびAES暗号化プロバイダー(プロバイダータイプ24 aka 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);

次に、SHA-256をサポートするプロバイダーの新しいRSACryptoServiceProviderインスタンスを作成します。

var key = new RSACryptoServiceProvider(
    new CspParameters(24 /* PROV_RSA_AES */));
key.PersistKeyInCsp = false;

そして秘密鍵をそれにインポートします:

key.FromXmlString(exportedKeyMaterial);

SignedXmlインスタンスを作成したら、cert.PrivateKeyではなくkeyを使用するように指示します。

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();
31
softwariness

エクスポートと再インポートはすでに 回答として与えられています ですが、他にもいくつか知っておくべきオプションがあります。

1. GetRSAPrivateKeyおよび.NET 4.6.2を使用します(現在プレビュー中)

GetRSAPrivateKey (拡張)メソッドは、キーとプラットフォームの「利用可能な最良のタイプ」のRSAインスタンスを返します(「誰もが知っている」PrivateKeyプロパティがRSACryptoServiceProviderを返すのとは対照的)。

すべてのRSA秘密鍵の99.99(etc)%で、このメソッドから返されたオブジェクトはSHA-2署名を生成できます。

このメソッドは.NET 4.6(.0)で追加されましたが、この場合は4.6.2の要件が存在します。これは、GetRSAPrivateKeyから返されたRSAインスタンスがSignedXmlで機能しなかったためです。それは 固定される (162556)以来です。

2.エクスポートせずにキーを再度開きます

私は個人的に、このアプローチは(現在はレガシー)PrivateKeyプロパティとRSACryptoServiceProviderクラスを使用しているため、好きではありません。ただし、これには.NET Frameworkのすべてのバージョンで作業できるという利点があります(ただし、RSACryptoServiceProviderはWindows専用であるため、Windows以外のシステムでは.NET Coreではありません)。

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);
}

RSACryptoServiceProviderとしてcert.PrivateKeyキャストを既に持っている場合は、UpgradeCspを介して送信できます。これにより既存のキーが開かれるため、ディスクに書き込まれる余分な情報はありません。既存のキーと同じ権限を使用し、エクスポートを行う必要はありません。

ただし、(注意!)PersistKeyInCsp = falseを設定しないでください。これにより、クローンが閉じられたときに元のキーが消去されます。

15
bartonjs