没有办法对框架类型执行此操作。 BouncyCastle 或其他库也许可以实现。
.NET Core 2.0 增加了通过扩展方法将证书和密钥对象合并在一起(到新的 X509Certificate2 对象中)的能力:
X509Certificate2 mergedCert = cert.CopyWithPrivateKey(rsaPrivateKey);
X509Certificate2 mergedCert = cert.CopyWithPrivateKey(dsaPrivateKey);
X509Certificate2 mergedCert = cert.CopyWithPrivateKey(ecdsaPrivateKey);
但这需要专门为 netcoreapp20(不是 netstandard20)编译。
框架类型也没有办法从二进制表示中加载关键对象(CngKey.Import 除外,但仅适用于 Windows),只能从预解析的结构(RSAParameters、@ 987654325@, ECParameters)。
在 Linux 上实现此目标的最简单方法(如果 BouncyCastle 无法帮助您)是使用 System.Process 产生类似于 openssl pkcs12 -export -out tmp.pfx -in tmp.cer -inkey tmp.key -password pass:"" 的调用。
在 Windows 上,您可以使用 CngKey.Import 和 P/Invoke CertSetCertificateContextProperty(对于 CERT_NCRYPT_KEY_HANDLE_PROP_ID (78))然后在变异证书上调用 cert.Export。
更新 (2020-09-30): 对于 .NET 3.0,这相当简单(不回答“使用 .NET Standard”的原始问题,因为它需要为 netcoreapp3 编译。 0 或更高版本),并且还可以轻松地使用 .NET 5.0 添加对 PEM 编码密钥的支持。
此代码检查密钥文件是否使用 .NET 5.0 PemEncoding 类进行 PEM 编码(而不是二进制/DER 编码),然后使用支持的格式加载私钥,匹配私钥直到证书,然后出口。 .NET Core 3.0 中添加了关键的导入方法。
private enum KeyFileKinds
{
None = 0,
Pkcs8,
EncryptedPkcs8,
RsaPrivateKey,
Any = -1,
}
public static byte[] MakePfx(string certPath, string keyPath, string exportPassword)
{
using X509Certificate2 cert = new X509Certificate2(certPath);
byte[] keyBytes;
KeyFileKinds kinds;
ReadOnlySpan<char> keyFileText = File.ReadAllText(keyPath).AsSpan();
// PemEncoding.TryFind requires net5.0+
if (PemEncoding.TryFind(keyFileText, out PemFields pemFields))
{
keyBytes = new byte[pemFields.DecodedDataLength];
if (!Convert.TryFromBase64Chars(keyFileText[pemFields.Base64Data], keyBytes, out int written) ||
written != keyBytes.Length)
{
Debug.Fail("PemEncoding.TryFind and Convert.TryFromBase64Chars disagree on Base64 encoding");
throw new InvalidOperationException();
}
ReadOnlySpan<char> label = keyFileText[pemFields.Label];
if (label.SequenceEqual("PRIVATE KEY"))
{
kinds = KeyFileKinds.Pkcs8;
}
else if (label.SequenceEqual("ENCRYPTED PRIVATE KEY"))
{
kinds = KeyFileKinds.EncryptedPkcs8;
}
else if (label.SequenceEqual("RSA PRIVATE KEY"))
{
kinds = KeyFileKinds.RsaPrivateKey;
}
else
{
throw new NotSupportedException($"The PEM file type '{label.ToString()}' is not supported.");
}
}
else
{
kinds = KeyFileKinds.Any;
keyBytes = File.ReadAllBytes(keyPath);
}
RSA rsa = null;
ECDsa ecdsa = null;
DSA dsa = null;
switch (cert.GetKeyAlgorithm())
{
case "1.2.840.113549.1.1.1":
rsa = RSA.Create();
break;
case "1.2.840.10045.2.1":
ecdsa = ECDsa.Create();
break;
case "1.2.840.10040.4.1":
dsa = DSA.Create();
break;
default:
throw new NotSupportedException($"The certificate key algorithm '{cert.GetKeyAlgorithm()}' is unknown");
}
AsymmetricAlgorithm anyAlg = rsa ?? ecdsa ?? (AsymmetricAlgorithm)dsa;
bool loaded = false;
int bytesRead;
using (rsa)
using (ecdsa)
using (dsa)
{
if (!loaded && rsa != null && kinds.HasFlag(KeyFileKinds.RsaPrivateKey))
{
try
{
rsa.ImportRSAPrivateKey(keyBytes, out bytesRead);
loaded = bytesRead == keyBytes.Length;
}
catch (CryptographicException)
{
}
}
if (!loaded && kinds.HasFlag(KeyFileKinds.Pkcs8))
{
try
{
anyAlg.ImportPkcs8PrivateKey(keyBytes, out bytesRead);
loaded = bytesRead == keyBytes.Length;
}
catch (CryptographicException)
{
}
}
if (!loaded && kinds.HasFlag(KeyFileKinds.EncryptedPkcs8))
{
try
{
// This assumes that the private key was already exported
// with the same password that the PFX will be exported with.
// Not true? Add a parameter :).
anyAlg.ImportEncryptedPkcs8PrivateKey(exportPassword, keyBytes, out bytesRead);
loaded = bytesRead == keyBytes.Length;
}
catch (CryptographicException)
{
}
}
if (!loaded)
{
throw new InvalidOperationException("Could not load the key as any known format.");
}
X509Certificate2 withKey;
if (rsa != null)
{
withKey = cert.CopyWithPrivateKey(rsa);
}
else if (ecdsa != null)
{
withKey = cert.CopyWithPrivateKey(ecdsa);
}
else
{
Debug.Assert(dsa != null);
withKey = cert.CopyWithPrivateKey(dsa);
}
using (withKey)
{
return withKey.Export(X509ContentType.Pfx, exportPassword);
}
}
}
更新 (2020-10-09):之前的更新显示了 .NET Core 3.1 中更好的代码,但也显示了 .NET 5 中的一些前瞻性代码。如果证书文件在PEM 格式 (-----BEGIN CERTIFICIATE-----) 并且密钥文件采用 PEM 格式(BEGIN PRIVATE KEY / BEGIN RSA PRIVATE KEY / BEGIN EC PRIVATE KEY / BEGIN ENCRYPTED PRIVATE KEY),那么.NET 5 有一个更简单的方法:
using (X509Certificate2 certWithKey = X509Certificate2.CreateFromPemFile(certPath, keyPath))
{
return certWithKey.Export(X509ContentType.Pfx, exportPassword);
}
也可用作CreateFromPem(loadedCertPem, loadedKeyPem)、CreateFromEncryptedPem(loadedCertPem, loadedKeyPem, keyPassword) 和CreateFromEncryptedPemFile(certPath, keyPath, keyPassword)。