c# - 无法导出 RSA 私钥参数,不支持请求的操作

标签 c# certificate rsa cng

我有一个由另一方提供的证书文件,我正在将其加载到我的应用程序中,但无法导出其私钥参数。看起来证书使用的是 CNG 而不是 CryptoAPI,所以我无法直接访问私钥,只能使用 GetRSAPrivateKey() 方法。该方法返回 RSACngKey而不是 RSACryptoServiceProvider这是 RSA 的不同实现。问题是返回的 key 似乎丢失了 CngExportPolicies.AllowPlaintextExport在其导出策略中,因此我无法从此证书导出 RSA 参数。我可以通过生成一个缺少必要导出政策的新证书来重现这个问题:

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace TestRsaCngConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var oldCertificate = CreateCertificate();
            var oldCertificateBytes = oldCertificate.Export(X509ContentType.Pfx, "");
            var newCertificate = new X509Certificate2(oldCertificateBytes, "",
                X509KeyStorageFlags.Exportable | 
                X509KeyStorageFlags.MachineKeySet | 
                X509KeyStorageFlags.PersistKeySet);

            LogCertificate(oldCertificate, "old certificate"); // this fails
            LogCertificate(newCertificate, "new certificate"); // works only on Win10
            Console.ReadKey();
        }

        private static X509Certificate2 CreateCertificate()
        {
            var keyParams = new CngKeyCreationParameters();
            keyParams.KeyUsage = CngKeyUsages.Signing;
            keyParams.Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider;
            keyParams.ExportPolicy = CngExportPolicies.AllowExport; // here I don't have AllowPlaintextExport
            keyParams.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(2048), CngPropertyOptions.None));
            var cngKey = CngKey.Create(CngAlgorithm.Rsa, Guid.NewGuid().ToString(), keyParams);
            var rsaKey = new RSACng(cngKey);
            var req = new CertificateRequest("cn=mah_cert", rsaKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); // requires .net 4.7.2
            var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5));
            return cert;
        }

        private static void LogCertificate(X509Certificate2 certificate, string name)
        {
            Console.WriteLine("----- Testing " + name + " ------");

            try
            {
                var rsaPrivateKey = certificate.GetRSAPrivateKey();
                var parameters = rsaPrivateKey.ExportParameters(true);
                Console.WriteLine("Certificate private key RSA parameters were successfully exported.");

                var privateKey = certificate.PrivateKey;
                Console.WriteLine("Certificate private key is accessible.");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}

该程序在 Windows 10 上运行时显示以下输出:
----- Testing old certificate ------
System.Security.Cryptography.CryptographicException: The requested operation is not supported.

   at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
   at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
   at System.Security.Cryptography.RSACng.ExportParameters(Boolean includePrivateParameters)
   at TestRsaCngConsole.Program.LogCertificate(X509Certificate2 certificate, String name) in D:\Projects\TestRsaCngConsole\TestRsaCngConsole\Program.cs:line 44
----- Testing new certificate ------
Certificate private key RSA parameters were successfully exported.
Certificate private key is accessible.

所以第一个证书无法导出私钥,因为它的导出策略中缺少 AllowPlaintextExport 标志。但是在重新加载带有可导出标志的旧证书后,我可以很好地导出新证书参数。但是,它不适用于 Windows Server 2012 或 Windows Server 2016,并且会为这两个证书引发异常:
----- Testing old certificate ------
System.Security.Cryptography.CryptographicException: The requested operation is not supported.

   at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
   at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
   at System.Security.Cryptography.RSACng.ExportParameters(Boolean includePrivateParameters)
   at TestRsaCngConsole.Program.LogCertificate(X509Certificate2 certificate, String name) in D:\Projects\TestRsaCngConsole\TestRsaCngConsole\Program.cs:line 44
----- Testing new certificate ------
System.Security.Cryptography.CryptographicException: The requested operation is not supported.

   at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
   at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
   at System.Security.Cryptography.RSACng.ExportParameters(Boolean includePrivateParameters)
   at TestRsaCngConsole.Program.LogCertificate(X509Certificate2 certificate, String name) in D:\Projects\TestRsaCngConsole\TestRsaCngConsole\Program.cs:line 44

我需要能够修复证书并使导出 RSA 参数成为可能,即使证书最初缺少 AllowPlaintextExport。 Windows Server 上有什么不同,有没有办法修复证书?

最佳答案

不幸的是,在该状态下导出 key 的唯一方法是 P/Invoke 到 NCryptExportKey 以设置加密导出;然后通过 NCryptImportKey 将其导入新 key ,然后将导出策略设置为 AllowPlaintextExport。
从 .NET Core 3.0 开始,这会更容易:

using (RSA exportRewriter = RSA.Create())
{
    // Only one KDF iteration is being used here since it's immediately being
    // imported again.  Use more if you're actually exporting encrypted keys.
    exportRewriter.ImportEncryptedPkcs8PrivateKey(
        "password",
        rsa.ExportEncryptedPkcs8PrivateKey(
            "password",
            new PbeParameters(
                PbeEncryptionAlgorithm.Aes128Cbc,
                HashAlgorithmName.SHA256,
                1)),
        out _);

    return exportRewriter.ExportParameters(true);
}
用于导出加密的 .NET Core 代码位于 https://github.com/dotnet/corefx/blob/64477348da1ff57a43deb65a4b12d32986ed00bd/src/System.Security.Cryptography.Cng/src/System/Security/Cryptography/CngKey.Export.cs#L126-L237 ,这不是一个很好的 API,必须从 C# 调用。

关于c# - 无法导出 RSA 私钥参数,不支持请求的操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54483371/

相关文章:

php - 用 openssl_public_encrypt 加密的数据每次都不一样?

iphone - 代码签名错误: The identity 'iPhone Developer: x Xxxxx' doesn't match any identity in any profile

ssl - 无法使用 openssl 从 cer 文件创建 pfx 文件

c# - 使用 RSACryptoServiceProvider 加密时如何定义哈希大小?

c# - 如何检查目录或其任何子目录中是否存在特定文件

powershell - Powershell在以分配的执行模式执行代码之前要求确认

java - 如何在不丢失或更改任何数据的情况下安全地将 RSA 加密和解密的字节转换为字符串?

C# FTP 获取今天添加的文件

c# - 没有控制台窗口的 AppServiceBridge

c# - Protobuf-net 使用接口(interface)和抽象基类创建类型模型