RSA解密引发“访问被拒绝”异常

时间:2018-08-23 20:43:09

标签: encryption .net-core x509certificate .net-standard

我正在将某些.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可以使用相同的代码。我确认用户可以访问商店中证书的私钥。

是什么导致此访问被拒绝的异常?

1 个答案:

答案 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 /调用变得更容易)。