【问题标题】:RSA decrypt throws 'Access denied' exceptionRSA 解密引发“拒绝访问”异常
【发布时间】:2018-08-25 04:09:34
【问题描述】:

我正在将一些 .NET Framework 库切换到 .NET Standard。我的一个库使用本地计算机上的证书存储来处理 JSON Web 令牌 (JWT)。图书馆使用的是RSACryptoServiceProvider,现在好像是not recommended了。

因此,我转而使用 GetPublicKey()GetPrivateKey() 扩展方法,但我遇到了私钥问题。每当我在收到的私钥的 RSA 实例上调用 Decrypt 时:

{Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: 访问被拒绝 System.Security.Cryptography.RSACng.EncryptOrDecrypt(SafeNCryptKeyHandle key, Byte[] 输入, AsymmetricPaddingMode paddingMode, Void* paddingInfo, EncryptOrDecryptAction encryptOrDecrypt) 在 System.Security.Cryptography.RSACng.EncryptOrDecrypt(字节 [] 数据, RSAEncryptionPadding 填充,EncryptOrDecryptAction encryptOrDecrypt) 在 System.Security.Cryptography.RSACng.Decrypt(字节 [] 数据, 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 工作。我已验证用户有权访问商店中证书的私钥。

是什么导致了这个拒绝访问异常?

【问题讨论】:

  • 我认为 .NET 标准 2.0 包含 RSACryptoServiceProvider 是否不正确?如果是这样,你为什么要避免它?
  • @MaartenBodewes 似乎避免它已成为 .NET 社区的普遍建议。一个例子:stackoverflow.com/questions/41986995/implement-rsa-in-net-core.
  • 嗯,这是关于一个属性被忽略的问题。通常代码会在下面使用相同的功能。
  • 感谢您的意见@MaartenBodewes。我用来处理 JWT 的库需要 RSA 对象,如果使用 RSACryptoServiceProvider 则会抛出。所以我还在寻找解决方案。
  • @JustinHelgerson ((RSACng)privateKey).Key.KeyUsages 说这是一个仅签名密钥?

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


【解决方案1】:

问题似乎是在创建(或导入)私钥时,它被标记为仅签名密钥。使用 CNG 密钥可以(并且在这种特定情况下,已经)通过检查 CngKey 对象 (((RSACng)privateKey).Key.KeyUsages) 的 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/Invoking 变得更容易完成)。

【讨论】:

  • 顺便说一句...我无法在 .NET Core 中转换为 RSACryptoServiceProvidercert.PrivateKey as RSACryptoServiceProvider 始终返回 null)。所以...我启动了 LINQPad 并获得了密钥号 ((certMatches[0].PrivateKey as RSACryptoServiceProvider).CspKeyContainerInfo.KeyNumber),即 2。
  • 另外,我应该提一下,在切换到 .NET Core 之前,我必须调用 ExportCspBlob(true) 并导入 RSACryptoServiceProvider 以使用任何私钥。
  • @JustinHelgerson 啊,这是一个重要的细节。事实证明,我有 AT_KEYEXCHANGE 和 AT_SIGNATURE 倒退(更新了“序言”)。在 CAPI 中修复它是……不同的。 (容易一点,也难一点)。如果您曾经进行导出/导入,当然可以继续这样做。
  • 抱歉,这有点超出我的驾驶室了。看起来我可以使用GetRSAPrivateKey 方法,然后导出参数,调用RSA.Create,然后导入参数。你在 CAPI 中修复它是什么意思?老实说,我从来没有完全理解为什么我必须首先导出/导入(但它起作用了,我不得不转向其他要求)。非常感谢您迄今为止的帮助。
猜你喜欢
  • 1970-01-01
  • 2015-05-26
  • 1970-01-01
  • 1970-01-01
  • 2012-05-19
  • 2011-04-02
  • 2014-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多