更新(2021-01-12):对于.NET 5来说,这非常容易。.NET Core 3.0甚至可以完成大部分工作。最初的答案是在.NET Core 1.1是最新版本的.NET Core时编写的。它解释了这些新方法在底层的操作。
更新(2023-08-30):而.NET 7则添加了直接转换为PEM的方法,使PEM输出变得更加简单。
.NET 7+:
string certificatePem = cert.ExportCertificatePem();
AsymmetricAlgorithm key = cert.GetRSAPrivateKey() ?? cert.GetECDsaPrivateKey();
string pubKeyPem = key.ExportSubjectPublicKeyInfoPem();
string privKeyPem = key.ExportPkcs8PrivateKeyPem();
.NET 7 还添加了 PemEncoding.WriteString,使得 .NET 5 风格的代码可以转换为 string
而不是 char[]
。
.NET 5+:
byte[] certificateBytes = cert.RawData;
char[] certificatePem = PemEncoding.Write("CERTIFICATE", certificateBytes);
AsymmetricAlgorithm key = cert.GetRSAPrivateKey() ?? cert.GetECDsaPrivateKey();
byte[] pubKeyBytes = key.ExportSubjectPublicKeyInfo();
byte[] privKeyBytes = key.ExportPkcs8PrivateKey();
char[] pubKeyPem = PemEncoding.Write("PUBLIC KEY", pubKeyBytes);
char[] privKeyPem = PemEncoding.Write("PRIVATE KEY", privKeyBytes);
new string(char[])
可以将这些字符数组转换为 System.String
实例,如果需要的话。
对于加密的 PKCS#8,仍然很容易,但是你必须在如何加密它方面做出一些选择:
byte[] encryptedPrivKeyBytes = key.ExportEncryptedPkcs8PrivateKey(
password,
new PbeParameters(
PbeEncryptionAlgorithm.Aes256Cbc,
HashAlgorithmName.SHA256,
iterationCount: 100_000))
.NET Core 3.0, .NET Core 3.1:
这与.NET 5的答案相同,只是PemEncoding
类尚不存在。但没关系,旧答案中有一个用于PEM格式化的起点(尽管"CERTIFICATE"和cert.RawData
需要从参数中获取)。
.NET Core 3.0是添加了额外密钥格式导出和导入方法的版本。
.NET Core 2.0, .NET Core 2.1:
与原始答案相同,只是您不需要编写DER编码器。您可以使用System.Formats.Asn1 NuGet package。
原始答案(.NET Core 1.1是最新选项):
答案介于“不”和“不完全是”之间。
我假设您不希望在public.pub
和private.key
的顶部有p12输出的冗余内容。
public.pub
只是证书。 openssl
命令行实用程序更喜欢PEM编码的数据,因此我们将编写一个PEM编码的证书(请注意,这是一个证书,而不是公钥。它包含一个公钥,但本身不是公钥):
using (var cert = new X509Certificate2(someBytes, pass))
{
StringBuilder builder = new StringBuilder();
builder.AppendLine("-----BEGIN CERTIFICATE-----");
builder.AppendLine(
Convert.ToBase64String(cert.RawData, Base64FormattingOptions.InsertLineBreaks));
builder.AppendLine("-----END CERTIFICATE-----");
return builder.ToString();
}
私钥更难处理。假设该密钥是可导出的(如果您使用的是Windows或macOS,则不可导出,因为您没有声明X509KeyStorageFlags.Exportable),您可以使用privateKey.ExportParameters(true)获取参数。但现在您必须将其写下来。
RSA私钥将被写入一个PEM编码的文件中,其标签为"RSA PRIVATE KEY",有效负载为ASN.1(ITU-T X.680)RSAPrivateKey(PKCS#1 / RFC3447)结构,通常为DER编码(ITU-T X.690)--尽管由于它没有签名,所以没有特定的DER限制,但许多读者可能会假设为DER编码。
或者,它可以是PKCS#8(
RFC 5208)的PrivateKeyInfo(标签:"PRIVATE KEY"),或者EncryptedPrivateKeyInfo(标签:"ENCRYPTED PRIVATE KEY")。由于EncryptedPrivateKeyInfo包装了PrivateKeyInfo,而PrivateKeyInfo封装了RSAPrivateKey,所以我们就从这里开始。
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER,
publicExponent INTEGER,
privateExponent INTEGER,
prime1 INTEGER,
prime2 INTEGER,
exponent1 INTEGER,
exponent2 INTEGER,
coefficient INTEGER,
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
现在忽略关于otherPrimeInfos的部分。
exponent1
是DP,
exponent2
是DQ,而
coefficient
是InverseQ。
我们来使用一个
预发布的384位RSA密钥。
RFC 3447说我们想要Version=0。其他所有信息都来自结构本身。
// SEQUENCE (RSAPrivateKey)
30 xa [ya [za]]
// INTEGER (Version=0)
02 01
00
// INTEGER (modulus)
// Since the most significant bit of the most significant content byte is set,
// add a padding 00 byte.
02 31
00
DA CC 22 D8 6E 67 15 75 03 2E 31 F2 06 DC FC 19
2C 65 E2 D5 10 89 E5 11 2D 09 6F 28 82 AF DB 5B
78 CD B6 57 2F D2 F6 1D B3 90 47 22 32 E3 D9 F5
// INTEGER publicExponent
02 03
01 00 01
// INTEGER (privateExponent)
// high bit isn't set, so no padding byte
02 30
7A 59 BD 02 9A 7A 3A 9D 7C 71 D0 AC 2E FA 54 5F
1F 5C BA 43 BB 43 E1 3B 78 77 AF 82 EF EB 40 C3
8D 1E CD 73 7F 5B F9 C8 96 92 B2 9C 87 5E D6 E1
// INTEGER (prime1)
// high bit is set, pad.
02 19
00
FA DB D7 F8 A1 8B 3A 75 A4 F6 DF AE E3 42 6F D0
FF 8B AC 74 B6 72 2D EF
// INTEGER (prime2)
// high bit is set, pad.
02 19
00
DF 48 14 4A 6D 88 A7 80 14 4F CE A6 6B DC DA 50
D6 07 1C 54 E5 D0 DA 5B
// INTEGER (exponent1)
// no padding
02 18
24 FF BB D0 DD F2 AD 02 A0 FC 10 6D B8 F3 19 8E
D7 C2 00 03 8E CD 34 5D
// INTEGER (exponent2)
// padding required
02 19
00
85 DF 73 BB 04 5D 91 00 6C 2D 45 9B E6 C4 2E 69
95 4A 02 24 AC FE 42 4D
// INTEGER (coefficient)
// no padding
02 18
1A 3A 76 9C 21 26 2B 84 CA 9C A9 62 0F 98 D2 F4
3E AC CC D4 87 9A 6F FD
现在我们计算进入RSAPrivateKey结构的字节数。我计算出是0xF2(242)。由于这个数大于0x7F,我们需要使用多字节长度编码:
81 F2
。
所以现在,通过字节数组
30 81 F2 02 01 00 ... 9A 6F FD
,你可以将其转换为多行Base64,并用"RSA PRIVATE KEY" PEM装甲包裹起来。但也许你想要一个PKCS#8。
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] IMPLICIT Attributes OPTIONAL }
Version ::= INTEGER
PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
PrivateKey ::= OCTET STRING
所以,让我们再做一次吧... RFC说我们在这里也想要version=0。AlgorithmIdentifier可以在
RFC5280中找到。
// SEQUENCE (PrivateKeyInfo)
30 xa [ya [za]]
// INTEGER (Version=0)
02 01
00
// SEQUENCE (PrivateKeyAlgorithmIdentifier / AlgorithmIdentifier)
30 xb [yb [zb]]
// OBJECT IDENTIFIER id-rsaEncryption (1.2.840.113549.1.1.1)
06 09 2A 86 48 86 F7 0D 01 01 01
// NULL (per RFC 3447 A.1)
05 00
// OCTET STRING (aka byte[]) (PrivateKey)
04 81 F5
[the previous value here,
note the length here is F5 because of the tag and length bytes of the payload]
回填长度:
“b”系列是13(0x0D),因为它只包含预定长度的内容。
“a”系列现在是(2 + 1)+(2 + 13)+(3 + 0xF5)= 266(0x010A)。
30 82 01 0A 02 01 00 30 0D ...
现在你可以将它作为“私钥”进行PEM格式化。
加密它?那是完全不同的事情。