更新 (2021-01-12):对于 .NET 5,这很容易。 .NET Core 3.0 甚至可以实现大部分目标。最初的答案是在 .NET Core 1.1 是 .NET Core 的最新版本时编写的。它解释了这些新方法在幕后的作用。
.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[]) 可以根据需要将这些 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-ifier 的开头(尽管“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 是最新的选项):
答案介于“不”和“不是真的”之间。
我假设您不希望 p12 输出粘在 public.pub 和 private.key 的顶部。
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,后者封装了 RSAPrivateKey,我们就从这里开始。
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
现在忽略关于 otherPrimeInfos 的部分。 exponent1 是 DP,exponent2 是 DQ,coefficient 是 InverseQ。
让我们使用pre-published 384-bit RSA key。
RFC 3447 说我们想要 Version=0。其他一切都来自结构。
// SEQUENCE (RSAPrivateKey)
30 xa [ya [za]]
// INTEGER (Version=0)
02 01
00
// INTEGER (modulus)
// Since the most significant bit if 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
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 (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。算法标识符可以在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。
加密它?那是完全不同的球赛。