无法导出 RSA 私钥参数,不支持请求的操作
Cannot export RSA private key parameters, the requested operation is not supported
我有一个由另一方提供的证书文件,我正在将其加载到我的应用程序中,但无法导出其私钥参数。看起来证书使用的是 CNG 而不是 CryptoAPI,所以我无法直接访问私钥,只能使用 GetRSAPrivateKey() 方法。 returns RSACngKey
而不是 RSACryptoServiceProvider
方法,后者是 RSA 的不同实现。问题是返回的密钥在其导出策略中似乎缺少 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());
}
}
}
}
当 运行 on 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 服务器有什么不同,有没有办法修复证书?
不幸的是,在该状态下导出密钥的唯一方法是 P/Invoke 进入 NCryptExportKey 以设置加密导出;然后通过 NCryptImportKey 将其导入到新密钥中,然后将导出策略设置为 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,必须从 C# 调用不是很好API。
这是 .NET Framework 的解决方案:
X509Certificate2 cert = new X509Certificate2("certificate.pfx", "password", X509KeyStorageFlags.Exportable);
我有一个由另一方提供的证书文件,我正在将其加载到我的应用程序中,但无法导出其私钥参数。看起来证书使用的是 CNG 而不是 CryptoAPI,所以我无法直接访问私钥,只能使用 GetRSAPrivateKey() 方法。 returns RSACngKey
而不是 RSACryptoServiceProvider
方法,后者是 RSA 的不同实现。问题是返回的密钥在其导出策略中似乎缺少 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());
}
}
}
}
当 运行 on 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 服务器有什么不同,有没有办法修复证书?
不幸的是,在该状态下导出密钥的唯一方法是 P/Invoke 进入 NCryptExportKey 以设置加密导出;然后通过 NCryptImportKey 将其导入到新密钥中,然后将导出策略设置为 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,必须从 C# 调用不是很好API。
这是 .NET Framework 的解决方案:
X509Certificate2 cert = new X509Certificate2("certificate.pfx", "password", X509KeyStorageFlags.Exportable);