c# - 将 X509 证书中的私钥/公钥导出到 PEM

标签 c# .net-core pem x509certificate2

有什么方便的方法可以使用 .NET Core 从 PEM 格式的 .p12 证书中导出私钥/公钥?不在低级别处理字节?我在谷歌上搜索了几个小时,几乎没有任何东西在 .net 核心中可用,或者它没有在任何地方记录..

让我们有一个 X509Certificate2

var cert = new X509Certificate2(someBytes, pass);
var privateKey = cert.GetRSAPrivateKey();
var publicKey = cert.GetRSAPublicKey();
// assume everything is fine so far

现在我需要将 key 导出为两个单独的 PEM key 。我已经在 BouncyCaSTLe 中尝试过 PemWriter,但这些类型与 Core 的 System.Security.Cryptography 不兼容......运气不好。

换句话说,我正在寻找一种写法:

$ openssl pkcs12 -in path/to/cert.p12 -out public.pub -clcerts -nokeys
$ openssl pkcs12 -in path/to/cert.p12 -out private.key -nocerts

有人有想法吗?

谢谢

最佳答案

更新(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 核心 3.0、.NET 核心 3.1:

这与 .NET 5 答案相同,只是 PemEncoding 类尚不存在。但这没关系,旧答案中的 PEM-ifier 开始(尽管“CERTIFICATE”和 cert.RawData)需要来自参数)。

.NET Core 3.0 是添加了额外的 key 格式导出和导入方法的版本。

.NET 核心 2.0、.NET 核心 2.1:

与原始答案相同,只是您不需要编写 DER 编码器。您可以使用 System.Formats.Asn1 NuGet package .

原始答案(.NET Core 1.1 是最新的选项):

答案介于“不”和“不是”之间。

我假设您不希望 public.pubprivate.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();
}

私钥更难。假设 key 是可导出的(如果您使用的是 Windows 或 macOS,则不是,因为您没有声明 X509KeyStorageFlags.Exportable),您可以使用 privateKey 获取参数。导出参数(真)。但现在你必须把它写下来。

RSA 私钥被写入 PEM 编码文件,其标签为“RSA PRIVATE KEY”,有效负载为 ASN.1 (ITU-T X.680)RSAPrivateKey (PKCS#1/RFC3447) 结构,通常为 DER- encoded ( ITU-T X.690 ) -- 虽然因为它没有签名所以没有特定的 DER 限制,但许多读者可能会假设 DER。

或者,它可以是 PKCS#8 ( RFC 5208 ) PrivateKeyInfo(标签:“私钥”)或 EncryptedPrivateKeyInfo(标签:“加密私钥”)。由于 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。 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 作为“私钥”。

加密?这是一个完全不同的球赛。

关于c# - 将 X509 证书中的私钥/公钥导出到 PEM,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43928064/

相关文章:

c# - 从 C# 线程内的非托管 dll 运行函数

C# - 重写对 Action<> 的委托(delegate)调用

azure - 如何将请求重定向到通过 Azure 应用服务托管的不同 Web 应用

.net-core - 404 尝试将上游路径路由到 Ocelot 中的下游路径

java - SSL 套接字连接错误

java - Kafka 不会从 PEM 证书开始

linux - AWS_EC2 ssh 权限被拒绝(公钥)

c# - 在类而不是类的集合上使用 foreach

c# - 以下两个语句有什么区别?

c# - 将 mvc 5 adfs 转换为 .net 核心 adfs