RSA 解密抛出 'Access denied' 异常
RSA decrypt throws 'Access denied' exception
我正在将一些 .NET Framework 库切换到 .NET Standard。我的一个库使用本地计算机上的证书存储来处理 JSON Web 令牌 (JWT)。图书馆使用的是 RSACryptoServiceProvider
,现在好像是 。
因此,我转而使用 GetPublicKey()
和 GetPrivateKey()
扩展方法,但私钥有问题。每当我在收到的私钥的 RSA 实例上调用 Decrypt
时:
{Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException:
Access denied at
System.Security.Cryptography.RSACng.EncryptOrDecrypt(SafeNCryptKeyHandle
key, Byte[] input, AsymmetricPaddingMode paddingMode, Void*
paddingInfo, EncryptOrDecryptAction encryptOrDecrypt) at
System.Security.Cryptography.RSACng.EncryptOrDecrypt(Byte[] data,
RSAEncryptionPadding padding, EncryptOrDecryptAction encryptOrDecrypt)
at System.Security.Cryptography.RSACng.Decrypt(Byte[] data,
RSAEncryptionPadding padding)
这是导致异常的代码的简短示例:
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
。我验证了用户可以访问商店中证书的私钥。
是什么导致此访问被拒绝异常?
问题似乎是当创建(或导入)私钥时,它被标记为仅签名密钥。通过检查 CngKey
对象 (((RSACng)privateKey).Key.KeyUsages
) 的 KeyUsages
属性,可以使用 CNG 密钥(并且,在这种特定情况下,已经)验证这一点。
在继续之前,请查看您的密钥的 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();
}
从技术上讲,在密钥为 "finalized"(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,则涉及更复杂的复杂规则(直接 P/Invoking 可能更容易做到)。
我正在将一些 .NET Framework 库切换到 .NET Standard。我的一个库使用本地计算机上的证书存储来处理 JSON Web 令牌 (JWT)。图书馆使用的是 RSACryptoServiceProvider
,现在好像是
因此,我转而使用 GetPublicKey()
和 GetPrivateKey()
扩展方法,但私钥有问题。每当我在收到的私钥的 RSA 实例上调用 Decrypt
时:
{Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Access denied at System.Security.Cryptography.RSACng.EncryptOrDecrypt(SafeNCryptKeyHandle key, Byte[] input, AsymmetricPaddingMode paddingMode, Void* paddingInfo, EncryptOrDecryptAction encryptOrDecrypt) at System.Security.Cryptography.RSACng.EncryptOrDecrypt(Byte[] data, RSAEncryptionPadding padding, EncryptOrDecryptAction encryptOrDecrypt) at System.Security.Cryptography.RSACng.Decrypt(Byte[] data, RSAEncryptionPadding padding)
这是导致异常的代码的简短示例:
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
。我验证了用户可以访问商店中证书的私钥。
是什么导致此访问被拒绝异常?
问题似乎是当创建(或导入)私钥时,它被标记为仅签名密钥。通过检查 CngKey
对象 (((RSACng)privateKey).Key.KeyUsages
) 的 KeyUsages
属性,可以使用 CNG 密钥(并且,在这种特定情况下,已经)验证这一点。
在继续之前,请查看您的密钥的 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();
}
从技术上讲,在密钥为 "finalized"(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,则涉及更复杂的复杂规则(直接 P/Invoking 可能更容易做到)。