Export private/public keys from X509 certificate to PEM

╄→尐↘猪︶ㄣ 提交于 2019-12-17 16:57:25

问题


is there any convenient way to export private/public keys from .p12 certificate in PEM format using .NET Core? Without manipulating with bytes at low level? I googled for hours and almost nothing is usable in .net core or it isn't documented anywhere..

Let's have an X509Certificate2

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

And now I need to export the keys as two separate PEM keys. I already tried PemWriter in BouncyCastle but the types are not compatibile with System.Security.Cryptography from Core .. no luck.

---Edit---

Another words, I'm finding a way how to write this:

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

Does anybody has an idea?

Thanks ...


回答1:


The answer is somewhere between "no" and "not really".

I'm going to assume that you don't want the p12 output gunk at the top of public.pub and private.key.

public.pub is just the certificate. The openssl commandline utility prefers PEM encoded data, so we'll write a PEM encoded certificate (note, this is a certificate, not a public key. It contains a public key, but isn't itself one):

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();
}

The private key is harder. Assuming the key is exportable (which, if you're on Windows or macOS, it isn't, because you didn't assert X509KeyStorageFlags.Exportable) you can get the parameters with privateKey.ExportParameters(true). But now you have to write that down.

An RSA private key gets written into a PEM encoded file whose tag is "RSA PRIVATE KEY" and whose payload is the ASN.1 (ITU-T X.680) RSAPrivateKey (PKCS#1 / RFC3447) structure, usually DER-encoded (ITU-T X.690) -- though since it isn't signed there's not a particular DER restriction, but many readers may be assuming DER.

Or, it can be a PKCS#8 (RFC 5208) PrivateKeyInfo (tag: "PRIVATE KEY"), or EncryptedPrivateKeyInfo (tag: "ENCRYPTED PRIVATE KEY"). Since EncryptedPrivateKeyInfo wraps PrivateKeyInfo, which encapsulates RSAPrivateKey, we'll just start there.

  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
  }

Now ignore the part about otherPrimeInfos. exponent1 is DP, exponent2 is DQ, and coefficient is InverseQ.

Let's work with a pre-published 384-bit RSA key.

RFC 3447 says we want Version=0. Everything else comes from the structure.

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

Now we count up the number of bytes that went into the RSAPrivateKey structure. I count 0xF2 (242). Since that's bigger than 0x7F we need to use multi-byte length encoding: 81 F2.

So now with the byte array 30 81 F2 02 01 00 ... 9A 6F FD you could convert that to multi-line Base64 and wrap it in "RSA PRIVATE KEY" PEM armor. But maybe you want a PKCS#8.

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

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

So, let's do it again... The RFC says we want version=0 here, too. AlgorithmIdentifier can be found in 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]

Backfill the lengths:

The "b" series is 13 (0x0D), since it only contains things of pre-determined length.

The "a" series is now (2 + 1) + (2 + 13) + (3 + 0xF5) = 266 (0x010A).

30 82 01 0A  02 01 00 30  0D ...

Now you can PEM that as "PRIVATE KEY".

Encrypting it? That's a whole different ballgame.




回答2:


I figured out a solution that works well. I could not find an EXACT example of how to go from certificate store to pem file in windows. Granted, this may not work for some certificates, but if you are working with one you have created yourself (for example, if you just need security between two machines you control that the end user won't see) this is a good way of going back to pem / pk (linux style).

I utilized the utilities found at 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();
}


来源:https://stackoverflow.com/questions/43928064/export-private-public-keys-from-x509-certificate-to-pem

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!