首页 > 解决方案 > 使用 RSACng 对与 RSACryptoServiceProvider 兼容的数据进行签名

问题描述

我正在尝试将一些较旧的 CAPI 代码转换为使用 CNG,特别是为了使用临时私钥来补充证书。(据我了解,CAPI 不支持。)

我们使用证书的私钥 (PK) 对数据进行签名。我希望这两种实现能够产生兼容的输出,但令我惊讶的是它们没有。

// Hydrate a cert with PK. Make PK a file-based key so we can get the container and use CAPI.
// (Note, although .NET Framework 4.8, the LangVersion is 8.0 so I can use using.)
using var cert = new X509Certificate2(testCertData, "passwd", X509KeyStorageFlags.MachineKeySet);
// Some arbitrary data to sign.
byte[] data = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };

byte[] signatureCsp, signatureCng;
// Sign with CAPI. This is what we're doing today.
// The cert PK's crypto provider doesn't support SHA256, so we build a different one.
var csp = (RSACryptoServiceProvider)cert.PrivateKey;
var cp = new CspParameters{
    KeyContainerName = csp.CspKeyContainerInfo.KeyContainerName,
    KeyNumber = csp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2,
};
using (var rsaCsp = new RSACryptoServiceProvider(cp)) {
    signatureCsp = rsaCsp.Sign(data);
}
// Sign with CNG. This is what I want to do.
using (var rsaCng = cert.GetRSAPrivateKey()) {
    signatureCng = rsaCng.Sign(data);
}
// The signatures are different. In fact they're different lengths, 128 and 256 bytes respectively.
Console.WriteLine(Convert.ToBase64String(signatureCsp));
Console.WriteLine(Convert.ToBase64String(signatureCng));

// But maybe they're compatible? Let's see if code which verifies the first can verify the second.
using (var provider = new RSACryptoServiceProvider(cp)) {
    var verifiedCsp = provider.Verify(data, signatureCsp);
    var verifiedCng = provider.Verify(data, signatureCng);
    Console.WriteLine("RSACryptoServiceProvider verified: {0}", verifiedCsp);
    Console.WriteLine("RSACng signature         verified: {0}", verifiedCng); // Nope. False.
}

上面使用以下扩展方法,为了统一,将这两种情况视为抽象 RSA 基本类型的实现并具有相同的加密参数。

public static byte[] Sign(this RSA rsa, byte[] buffer) {
    return rsa.SignData(buffer, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
public static bool Verify(this RSA rsa, byte[] buffer, byte[] signature) {
    return rsa.VerifyData(buffer, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}

我需要做什么来使用证书的 CNG 密钥对数据进行签名,以便 CAPI 密钥生成的签名的当前消费者可以验证输出?

标签: c#rsasignx509certificate2cng

解决方案


您对 RSA 类的使用是正确的,并且将为相应的密钥生成兼容的结果。

这里的问题是您将 PFX 打开到机器密钥存储中:

using var cert = new X509Certificate2(testCertData, "passwd", X509KeyStorageFlags.MachineKeySet);

但是当您重新打开密钥的 RSACryptoServiceProvider 版本时,您没有设置CspProviderFlags.UseMachineKeyStore标志。

var cp = new CspParameters{
    KeyContainerName = csp.CspKeyContainerInfo.KeyContainerName,
    KeyNumber = csp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2,
};

由于您没有设置机器密钥存储标志,它使用了用户密钥存储,并且“打开”密钥创建了一个新密钥(因为以前不存在具有该名称的密钥)。创建一次后,连续运行将重新打开相同的密钥(假设 PFX 导入具有一致的密钥名称)。

var cp = new CspParameters{
    KeyContainerName = csp.CspKeyContainerInfo.KeyContainerName,
    KeyNumber = csp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2,
    Flags = CspProviderFlags.UseMachineKeyStore | CspProviderFlags.UseExistingKey,
};

推荐阅读