将私钥/公钥从X509证书导出到PEM

有没有方便的方法从使用.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 

现在我需要将密钥导出为两个单独的PEM密钥。 我已经在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 

有人有想法吗?

谢谢 …

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

我将假设您不希望p12输出gunk位于public.pubprivate.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(标记:“加密的私钥”)。 由于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。

让我们使用预先发布的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 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说我们也想要版本= 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视为“私钥”。

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

我找到了一个运作良好的解决方案。 我在Windows中找不到如何从证书存储转到pem文件的确切示例。 当然,这可能不适用于某些证书,但如果您正在使用自己创建的证书(例如,如果您只需要控制最终用户将看不到的两台计算机之间的安全性),这是一种很好的方法。回到pem / pk(linux风格)。

我使用了http://www.bouncycastle.org/csharp/上的实用程序

 X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine); certStore.Open(OpenFlags.ReadOnly); X509Certificate2 caCert = certStore.Certificates.Find(X509FindType.FindByThumbprint, "3C97BF2632ACAB5E35B48CB94927C4A7D20BBEBA", true)[0]; RSACryptoServiceProvider pkey = (RSACryptoServiceProvider)caCert.PrivateKey; AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(pkey); using (TextWriter tw = new StreamWriter("C:\\private.pem")) { PemWriter pw = new PemWriter(tw); pw.WriteObject(keyPair.Private); tw.Flush(); }