我正在将某些.NET Framework库切换到.NET Standard。我的一个库使用本地计算机上的证书存储来处理JSON Web令牌(JWT)。该库正在使用RSACryptoServiceProvider
,似乎已经是not recommended。
结果,我切换到使用GetPublicKey()
和GetPrivateKey()
扩展方法,但是私钥有问题。每当我在收到的私钥的RSA实例上调用Decrypt
时:
{Internal.Cryptography.CryptoThrowHelper + WindowsCryptographicException: 拒绝访问 System.Security.Cryptography.RSACng.EncryptOrDecrypt(SafeNCryptKeyHandle 键,字节[]输入,AsymmetricPaddingMode paddingMode,无效* paddingInfo,EncryptOrDecryptAction cryptoOrDecrypt) System.Security.Cryptography.RSACng.EncryptOrDecrypt(Byte []数据, RSAEncryptionPadding填充,EncryptOrDecryptAction加密或解密) 在System.Security.Cryptography.RSACng.Decrypt(Byte []数据, RSAEncryptionPadding填充)
以下是导致异常的代码的简短示例:
public X509Certificate2 GetCert() {
using (var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine)) {
certStore.Open(OpenFlags.ReadOnly);
var certMatches = certStore.Certificates.Find(X509FindType.FindByThumbprint, CertificateThumbprint, false);
return certMatches[0];
}
}
var cert = GetCert();
var publicKey = cert.GetRSAPublicKey();
var encryptedBytes = publicKey.Encrypt(bytes, System.Security.Cryptography.RSAEncryptionPadding.OaepSHA256);
var privateKey = cert.GetRSAPrivateKey();
// Exception on this line. :(
var decryptedBytes = privateKey.Decrypt(encryptedBytes, System.Security.Cryptography.RSAEncryptionPadding.OaepSHA256);
使用RSACryptoServiceProvider
可以使用相同的代码。我确认用户可以访问商店中证书的私钥。
是什么导致此访问被拒绝的异常?
答案 0 :(得分:2)
问题似乎是私钥在创建(或导入)时被标记为仅签名密钥。使用CNG密钥,可以通过检查KeyUsages
对象(CngKey
)的((RSACng)privateKey).Key.KeyUsages
属性来验证(在这种情况下,已经验证)。
在继续之前,请查看密钥的RSACryptoServiceProvider
版本。 rsaCsp.CspParameters.KeyNumber
是我们真正想要的。
switch (rsaCsp.CspParameters.KeyNumber)
{
case 0:
You're on the CAPI-to-CNG bridge, new to Windows 10.
Keep going, this is the answer I answered.
break;
case 1:
This is a CAPI AT_KEYEXCHANGE key.
Things should just work...
break;
case 2:
This is a CAPI AT_SIGNATURE key, I don't understand why CAPI allowed decryption.
A different answer is required.
break;
default:
throw new ArgumentOutOfRangeException();
}
从技术上讲,在密钥“定稿”之后(CNG术语,而不是.NET术语),不能更改密钥用法。但是,如果密钥是可导出的,则可以使用类似的方法解决该问题
private static CngKey ResetKeyUsage(CngKey key)
{
CngKeyCreationParameters keyParameters = new CngKeyCreationParameters
{
ExportPolicy = key.ExportPolicy,
KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey,
};
if (key.IsMachineKey)
{
keyParameters.Parameters.Add(
key.GetProperty("Security Descr", (CngPropertyOptions)4));
keyParameters.KeyCreationOptions |= CngKeyCreationOptions.MachineKey;
}
CngKeyBlobFormat rsaPrivateBlob = new CngKeyBlobFormat("RSAPRIVATEBLOB");
keyParameters.Parameters.Add(
new CngProperty(
rsaPrivateBlob.Format,
key.Export(rsaPrivateBlob),
CngPropertyOptions.Persist));
CngAlgorithm alg = key.Algorithm;
string name = key.KeyName;
CngKey newKey = CngKey.Create(alg, name, keyParameters);
key.Dispose();
return newKey;
}
请注意,这确实应该是一次性操作,因为它无疑会对所有打开的键句柄造成不良影响。
如果ExportPolicy声明它是可导出的,但不是PlaintextExportable,则涉及更复杂的rigamarole(并且可能通过直接P /调用变得更容易)。