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

时间:2017-05-12 01:46:13

标签: 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

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

有人有想法吗?

谢谢...

4 个答案:

答案 0 :(得分:8)

答案介于" no"并且"不是真的"。

我将假设您不希望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),您可以使用{{1}获取参数}。但现在你必须写下来。

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包含了封装RSAPrivateKey的PrivateKeyInfo,我们只是从那里开始。

privateKey.ExportParameters(true)

现在忽略关于otherPrimeInfos的部分。 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 } 是DP,exponent1是DQ,exponent2是InverseQ。

让我们使用pre-published 384-bit RSA key

RFC 3447说我们想要Version = 0。其他一切都来自结构。

coefficient

现在我们计算进入RSAPrivateKey结构的字节数。我算上0xF2(242)。由于它大于0x7F,我们需要使用多字节长度编码:// 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

现在使用字节数组81 F2,您可以将其转换为多行Base64并将其包装在" RSA PRIVATE KEY" PEM装甲。但也许你想要一个PKCS#8。

30 81 F2 02 01 00 ... 9A 6F FD

所以,让我们再做一次...... RFC说我们也想要版本= 0。 AlgorithmIdentifier可以在RFC5280中找到。

  PrivateKeyInfo ::= SEQUENCE {
    version                   Version,
    privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
    privateKey                PrivateKey,
    attributes           [0]  IMPLICIT Attributes OPTIONAL }

  Version ::= INTEGER
  PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
  PrivateKey ::= OCTET STRING

回填长度:

" b" series是13(0x0D),因为它只包含预定长度的东西。

" a"系列现在是(2 + 1)+(2 + 13)+(3 + 0xF5)= 266(0x010A)。

// 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]

现在您可以将其作为" PRIVATE KEY"。

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

答案 1 :(得分:1)

X509certificate2->私有,公共和证书pems ...我刚刚发现您可以用5或6行外行代码来做到这一点!

有一个名为Chilkat的免费软件包(带有一些冷门商标)。它具有一些非常直观的Certificate Classes,下面是一些示例代码,说明如何创建自签名pfx格式的证书并将其导出到PEM!因此,这是一个X509Certificate2实例,该实例带有证书,相关的公钥和对其进行签名的私钥,然后将其导出为三个单独的Pem文件。一个用于证书(包括公钥),一个用于公钥,一个用于私钥。非常简单(花了一周的阅读时间才弄清楚,哈哈)。然后检出https://github.com/patrickpr/YAOG以获得一个不错的OpenSSL Windows Gui,以查看/创建证书(如结果屏幕截图所示)。

using Chilkat;
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;


namespace CertPractice
{
static public class CertificateUtilityExample
{

    public static X509Certificate2 GenerateSelfSignedCertificate()
    {


        string secp256r1Oid = "1.2.840.10045.3.1.7";  //oid for prime256v1(7)  other identifier: secp256r1
        
        string subjectName = "Self-Signed-Cert-Example";

        var ecdsa = ECDsa.Create(ECCurve.CreateFromValue(secp256r1Oid));

        var certRequest = new CertificateRequest($"CN={subjectName}", ecdsa, HashAlgorithmName.SHA256);

        //add extensions to the request (just as an example)
        //add keyUsage
        certRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true));

        X509Certificate2 generatedCert = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddYears(10)); // generate the cert and sign!
//----------------end certificate generation, ie start here if you already have an X509Certificate2 instance----------------


        X509Certificate2 pfxGeneratedCert = new X509Certificate2(generatedCert.Export(X509ContentType.Pfx)); //has to be turned into pfx or Windows at least throws a security credentials not found during sslStream.connectAsClient or HttpClient request...

        Chilkat.Cert chilkatVersionOfPfxGeneratedCert = new Chilkat.Cert(); // now use Chilcat Cert to get pems
        chilkatVersionOfPfxGeneratedCert.LoadPfxData(generatedCert.Export(X509ContentType.Pfx), null); // export as binary pfx to load into a Chilkat Cert

        PrivateKey privateKey = chilkatVersionOfPfxGeneratedCert.ExportPrivateKey(); // get the private key
        privateKey.SavePemFile(@"filepath"); //save the private key to a pem file
        
        Chilkat.PublicKey publicKey = chilkatVersionOfPfxGeneratedCert.ExportPublicKey(); //get the public key
        publicKey.SavePemFile(true, @"filepath"); //save the public key

        chilkatVersionOfPfxGeneratedCert.ExportCertPemFile(@"filepath"); //save the public Cert to pem file
        


        return pfxGeneratedCert;


    }
}

Screen shot of output pem files

答案 2 :(得分:0)

我找到了一个效果很好的解决方案。我找不到一个如何在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();
}

答案 3 :(得分:0)

基于@bartonjs 的知识(his answer),我写了一个小类,应该很容易使用。

所以现在也有一个完整的例子,无需使用外部dlls/nuget包

我唯一需要做的改变是:

  • 在创建 X509Certificate2 实例时,我必须将此“X509KeyStorageFlags.Exportable”添加到 StorageFlags,以便“ExportPkcs8PrivateKey()”方法不会失败。

通过我的课程,可以使用证书和私钥将 Let's Encrypt 证书从 PFX 格式转换为 PEM 格式

如何使用我的课程


def check_best_value(model, generator , callback , bs , ep , sh):
    bs_acc = []
    bs_val = []

    epoch_acc = []
    epoch_val = []

    shape_acc = []
    shape_val = []


    callback = EarlyStopping(monitor = 'val_loss' , patience = 1 , verbose = 1 , 
    restore_best_weights=True , mode = 'auto')

    for bs_size in bs:
        for ep_size in ep:
            for img_size in sh:
                train_gen = generator.flow_from_directory(TRAIN_DIR , 
                                                                target_size = (img_size , img_size),
                                                                batch_size = bs_size ,
                                                                class_mode = 'categorical',
                                                                subset = 'training',
                                                                seed = 14)
                
                val_gen = generator.flow_from_directory(TRAIN_DIR , 
                                                                target_size = (img_size , img_size),
                                                                batch_size = bs_size ,
                                                                class_mode = 'categorical',
                                                                subset = 'validation',
                                                                seed = 14)
                h = model.fit(train_gen,
                                steps_per_epoch=train_gen.samples // bs_size,
                                epochs=ep_size,
                                validation_data=val_gen,
                                validation_steps= val_gen.samples // bs_size,
                                callbacks = [callback],
                                verbose=0)
                
            shape_acc = shape_acc.append(h.history["accuracy"])
            shape_val = shape_val.append(h.history["val_accuracy"])

        epoch_acc = epoch_acc.append(h.history["accuracy"])
        epoch_val = epoch_val.append(h.history["val_accuracy"])                

    bs_acc = bs_acc.append(h.history["accuracy"])
    bs_val = bs_val.append(h.history["val_accuracy"])
    
    return bs_acc , bs_val , epoch_acc , epoch_val , shape_acc , shape_val 

我在那个班级背后的代码

var certificateLogic = new CertificateLogic("fileName.pfx", "privateKeyOfPfx");
certificateLogic.LoadCertificate();
certificateLogic.GenerateSaveCertificatePem();
certificateLogic.GenereateSavePrivateKeyPem();