jameshfisher
jameshfisher

Reputation: 36399

How can I convert a .p12 to a .pem containing an unencrypted PKCS#1 private key block?

I have a Certificates.p12 file that I wish to convert to a certificates.pem containing an unencrypted private key in PKCS#1 format. I have previously been able to do this by running:

openssl pkcs12 -in Certificates.p12 -out certificates.pem -nodes -clcerts

The resulting certificates.pem file has a PRIVATE KEY PEM block, as expected. However, the library I'm using does not understand this PEM block, because it expects it to be a PKCS#1 private key. The ASN.1 structure of a PKCS#1 private key is defined by RFC 3447 as:

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
}

The bad private key block in my certificates.pem does not have this PKCS#1 structure! Instead, its ASN.1 structure looks like this:

$ openssl asn1parse -i -in badprivatekey.pem
    0:d=0  hl=4 l=1212 cons: SEQUENCE
    4:d=1  hl=2 l=   1 prim:  INTEGER           :00
    7:d=1  hl=2 l=  13 cons:  SEQUENCE
    9:d=2  hl=2 l=   9 prim:   OBJECT            :rsaEncryption
   20:d=2  hl=2 l=   0 prim:   NULL
   22:d=1  hl=4 l=1190 prim:  OCTET STRING      [HEX DUMP]:308204A...very long hex...

What is the above format? The documentation for openssl pkcs12 only vaguely says that its output is "written in PEM format." I need a stronger guarantee that the private key PEM block is in PKCS#1 format.

The strange thing is that openssl rsa understands the strange format of the "bad" private key, and can convert it to the right PKCS#1 structure with:

openssl rsa -in badprivatekey.pem -out goodprivatekey.pem

Although openssl rsa understands the input file, the tool seems unable to tell me why, i.e. what the format of the input file is.

What is the output format of openssl pkcs12? Specifically what is the format of its private key block? How do I make openssl pkcs12 output a correct PKCS#1 private key?

Upvotes: 5

Views: 13085

Answers (1)

dave_thompson_085
dave_thompson_085

Reputation: 38771

Wow, that library assumes any PEM ending with PRIVATE KEY must be PKCS1?? That is horribly wrong. There are several xx PRIVATE KEY formats and only one of them is PKCS1, see below.

Meta: I think this is a dupe but I can't find it, so answering anyway.

OpenSSL supports FOUR different PEM formats for RSA private keys:

  • 'traditional' or 'legacy' unencrypted which is the PKCS1 format you want (https://www.rfc-editor.org/rfc/rfc8017#appendix-A.1.2) with PEM type RSA PRIVATE KEY (NOT just PRIVATE KEY)

  • 'traditional' or 'legacy' encrypted at PEM level with OpenSSL's (SSLeay's) custom scheme which uses quite poor (and unfixable) PBKDF; this has the same PEM type RSA PRIVATE KEY but added headers Proc-type and DEK-info

  • PKCS8 standard/generic unencrypted (https://www.rfc-editor.org/rfc/rfc5208#section-5) with PEM type PRIVATE KEY; this is a simple ASN.1 wrapper containing an identifier for the algorithm (i.e. an OID for RSA) plus an OCTET STRING containing the algorithm-dependent part which for RSA is PKCS1

  • PKCS8 standard/generic encrypted (https://www.rfc-editor.org/rfc/rfc5208#section-6) with PEM type ENCRYPTED PRIVATE KEY; this encrypts the PKCS8 data within the ASN.1 with an algorithm that normally defaults to at least PBKDF2-SHA1-2048 which is decent

Because PKCS8 is more flexible, and is standard (and fairly commonly used e.g. Java), and has better encryption, it is generally preferred; see the Notes section of the manpage for the PEM_read/write functions for keys and some but not all other things.

All OpenSSL functions that read a PEM private key can read any of these (given the correct password when necessary) but which they write varies depending on the function and to an extent options. As you note pkcs12 (import) (currently) writes PKCS8, but rsa (always) writes traditional/PKCS1.

Your options are:

  • use rsa (as you did), or in 1.1.0 pkey -traditional, to convert to traditional

  • use pkcs12 in a release before 1.0.0, like 0.9.8, when it wrote traditional formats (for multiple algorithms not just RSA). Of course using an obsolete and unsupported release may expose you to flaws and even vulnerabilities that were fixed in later releases.

  • extract the PKCS1 part out of the (unencrypted or decrypted) PKCS8. OpenSSL uses DER for keys which means the algorithm-dependent blob, which is the value of the last field in the last group, is always the contiguous last part of the data. As an example:

    # note using -passin on commandline can be insecure (see the man page)
    # but is used in these examples for simplicity; for real keys
    # often better to omit the option and let the program prompt

    $ openssl pkcs12 -in SO47599544.p12 -passin pass:sekrit -nocerts -nodes
    MAC verified OK
    Bag Attributes
        friendlyName: mykey
        localKeyID: 54 69 6D 65 20 31 35 31 32 31 37 30 38 39 39 33 33 37
    Key Attributes: <No Attributes>
    -----BEGIN PRIVATE KEY-----
    MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCnWIGH4p1FHOP2
    wtVbxuyvSHpBGd2v+7AyVHG4EJ/9WRoWN4+aGYUOGxzdTyDxk9e7DCTjuY6ciNTh
    Ph74LADfQQ0B8yGkRtKer3vO1Dg7+6ErCcIGgsfrhZqpuUfod4nSU/LnHXoAAgN5
    07LVvohrJMSRTA55jn356EDv31sz/dew1ThzHjYTShGbXh0/baqLxpmm4e8OixAL
    YV1glHnIdr4h6wrwkJ6TtNexc3xTLwtRf9k7pJPvg+viGh7RTqhbUcGSV+dLelNe
    653LbElHtAByz4Ti9e4vUKFuzxqsaWaSYGpzxF/pdthQawV/fTa9CjLGJFFrnVqc
    KT3TSJ19AgMBAAECggEAOmmubRwxAVrgR9YiW3LIUzbdVbQNqcwU6LyJJVLIRcrA
    TFkAiy21QAM+xBFG0oxklSncBpFSslkg1a61aLMTatpuC+wuJgWCp1lhwgRZzLY8
    v6UcUOF9nzx3jB7cdsyjEwOymfG0ECSjyfaXSfzD6YJgCsedldijKIRlhlVUpIS4
    YvdPPGQMXxLr9K8dkQ9o5yTQCrpey1/dNEo7lS17/uMV++dxmka5J+/dRcm2AAIc
    7dk6OX9MpGKPFODUyvFjdrWPR2qK25cmVW6hrhJuaPih+1eSd78UkR7OdoHBQEbJ
    5MoXSO0eTV4rhid+dX+ynwXA2OvaZbxcr7rlZXjaAQKBgQDybaKW32RHVmjKQDXC
    xyTdQTMJV7JClBKeXjqJxbgKDhKFTiapn7kNVbJ594n7twuxmTxxoN35gamsbe7q
    HjEesZZvb2vgLTXnqSvSXcl32CEi554VjlNP6+jZ5JBu/Gw7qObKuWBt+/gkrtCx
    d09xQllZlD354RyfS3+9jzdEXQKBgQCwttL+Gw2WEm4dPQrfxbasXKQ5hNSE42j+
    i0W26xv8o1lKQFip0A4YWidfI+Cvued944ZqCTvnPv6Z+JQzidHFjg6DXWgs1GK/
    olsh5xO0hoxAj1azx+11ZHKSb7Kni+jSJsz40U35mWE805HFijxzzQz45unuPZGr
    d8oqXIcroQKBgQCKuU32w7JgWAPy6DdbVBW2Pl70E6jADHdzBDy/JdMgfdj/Sy84
    lVuRU96jiJD+50nbwPIjm4gqBJaRQv8aHVjCVaDd94Zla7mS7O1UnbJxz812ac++
    SglGjJpcRTyZJfzRTt9yVg3mIe9nHlnxk3J0PyFd70Rfvv9f8BYS5OcdSQKBgBnb
    xug0ITrSm5ZftlWkYuS58bYQ/+AqPtTwoFTx9nhzlr9MxyyiK03Y82XypBBSzdMY
    FjUyALgH+c2iGF2qTy3vaaRDaNkWgxSzt04wuCt0fNV9pBxOpyrEdheDjMsDqCAI
    WXoXdqeNkDMMaopTfiEb4kgR0i1wiP5kWwrz2zvBAoGBAPELu0IH3jtvo849KeXW
    O6U1QlxdmWS6h/La1iVRHoE2U3pxAj39IDx4P6GMrgc9VLqRKLTO1Cu9giimO2jH
    8iryT5VTlrrINL3M4vXAFjSN/xwVsrLaw/mAQPOKBwNlDwxcCrlxnANnYXdrhk8n
    uNmZ2VH8flBFRpSbm9aisgMr
    -----END PRIVATE KEY-----
    $ openssl pkcs12 -in SO47599544.p12 -passin pass:sekrit -nocerts -nodes \
    | sed -e 1,/-BEGIN/d -e /-END/,\$d | openssl asn1parse
    MAC verified OK
     0:d=0  hl=4 l=1214 cons: SEQUENCE
     4:d=1  hl=2 l=   1 prim: INTEGER           :00
     7:d=1  hl=2 l=  13 cons: SEQUENCE
     9:d=2  hl=2 l=   9 prim: OBJECT            :rsaEncryption
    20:d=2  hl=2 l=   0 prim: NULL
    22:d=1  hl=4 l=1192 prim: OCTET STRING      [HEX DUMP]:308204A4[rest snipped]
    [that's PKCS8 with algo-dependent part in the OCTET STRING at offset 22]
    [with a tag-length header length of 4 (the hl=4)]

You can extract the PKCS1 part using asn1parse -strparse:

    $ openssl pkcs12 -in SO47599544.p12 -passin pass:sekrit -nocerts -nodes \
    | sed -e 1,/-BEGIN/d -e /-END/,\$d \
    | openssl asn1parse -strparse 22 -out SO47599544.raw -noout
    MAC verified OK
    $ openssl asn1parse -in SO47599544.raw -inform der
        0:d=0  hl=4 l=1188 cons: SEQUENCE
        4:d=1  hl=2 l=   1 prim: INTEGER           :00
        7:d=1  hl=4 l= 257 prim: INTEGER           :[snipped]
      268:d=1  hl=2 l=   3 prim: INTEGER           :010001
      273:d=1  hl=4 l= 256 prim: INTEGER           :[snipped]
    [rest snipped -- that's your PKCS1 format but in der so convert to pem]
    $ (echo -----BEGIN RSA PRIVATE KEY-----; openssl base64 -in SO47599544.raw; \
      echo -----END RSA PRIVATE KEY-----) | tee SO47599544.pem
    -----BEGIN RSA PRIVATE KEY-----
    MIIEpAIBAAKCAQEAp1iBh+KdRRzj9sLVW8bsr0h6QRndr/uwMlRxuBCf/VkaFjeP
    mhmFDhsc3U8g8ZPXuwwk47mOnIjU4T4e+CwA30ENAfMhpEbSnq97ztQ4O/uhKwnC
    BoLH64WaqblH6HeJ0lPy5x16AAIDedOy1b6IayTEkUwOeY59+ehA799bM/3XsNU4
    cx42E0oRm14dP22qi8aZpuHvDosQC2FdYJR5yHa+IesK8JCek7TXsXN8Uy8LUX/Z
    O6ST74Pr4hoe0U6oW1HBklfnS3pTXuudy2xJR7QAcs+E4vXuL1Chbs8arGlmkmBq
    c8Rf6XbYUGsFf302vQoyxiRRa51anCk900idfQIDAQABAoIBADpprm0cMQFa4EfW
    IltyyFM23VW0DanMFOi8iSVSyEXKwExZAIsttUADPsQRRtKMZJUp3AaRUrJZINWu
    tWizE2rabgvsLiYFgqdZYcIEWcy2PL+lHFDhfZ88d4we3HbMoxMDspnxtBAko8n2
    l0n8w+mCYArHnZXYoyiEZYZVVKSEuGL3TzxkDF8S6/SvHZEPaOck0Aq6Xstf3TRK
    O5Ute/7jFfvncZpGuSfv3UXJtgACHO3ZOjl/TKRijxTg1MrxY3a1j0dqituXJlVu
    oa4Sbmj4oftXkne/FJEeznaBwUBGyeTKF0jtHk1eK4YnfnV/sp8FwNjr2mW8XK+6
    5WV42gECgYEA8m2ilt9kR1ZoykA1wsck3UEzCVeyQpQSnl46icW4Cg4ShU4mqZ+5
    DVWyefeJ+7cLsZk8caDd+YGprG3u6h4xHrGWb29r4C0156kr0l3Jd9ghIueeFY5T
    T+vo2eSQbvxsO6jmyrlgbfv4JK7QsXdPcUJZWZQ9+eEcn0t/vY83RF0CgYEAsLbS
    /hsNlhJuHT0K38W2rFykOYTUhONo/otFtusb/KNZSkBYqdAOGFonXyPgr7nnfeOG
    agk75z7+mfiUM4nRxY4Og11oLNRiv6JbIecTtIaMQI9Ws8ftdWRykm+yp4vo0ibM
    +NFN+ZlhPNORxYo8c80M+Obp7j2Rq3fKKlyHK6ECgYEAirlN9sOyYFgD8ug3W1QV
    tj5e9BOowAx3cwQ8vyXTIH3Y/0svOJVbkVPeo4iQ/udJ28DyI5uIKgSWkUL/Gh1Y
    wlWg3feGZWu5kuztVJ2ycc/NdmnPvkoJRoyaXEU8mSX80U7fclYN5iHvZx5Z8ZNy
    dD8hXe9EX77/X/AWEuTnHUkCgYAZ28boNCE60puWX7ZVpGLkufG2EP/gKj7U8KBU
    8fZ4c5a/TMcsoitN2PNl8qQQUs3TGBY1MgC4B/nNohhdqk8t72mkQ2jZFoMUs7dO
    MLgrdHzVfaQcTqcqxHYXg4zLA6ggCFl6F3anjZAzDGqKU34hG+JIEdItcIj+ZFsK
    89s7wQKBgQDxC7tCB947b6POPSnl1julNUJcXZlkuofy2tYlUR6BNlN6cQI9/SA8
    eD+hjK4HPVS6kSi0ztQrvYIopjtox/Iq8k+VU5a6yDS9zOL1wBY0jf8cFbKy2sP5
    gEDzigcDZQ8MXAq5cZwDZ2F3a4ZPJ7jZmdlR/H5QRUaUm5vWorIDKw==
    -----END RSA PRIVATE KEY-----

Or you can convert the PKCS8 body to binary and just discard the first 22+4=26 bytes (since header len hl=4 from first asn1parse above):

    $ openssl pkcs12 -in SO47599544.p12 -passin pass:sekrit -nocerts -nodes \
    | sed -e 1,/-BEGIN/d -e /-END/,\$d \
    | openssl base64 -d | dd bs=1 skip=26 >SO47599544.raw
    MAC verified OK
    1192+0 records in
    1192+0 records out
    1192 bytes (1.2 kB) copied, 0.00892462 s, 134 kB/s
    [then convert to PEM with echo BEGIN;base64(encode);echo END as above]

PS: If it's important to only read the PKCS12 once, for example to avoid retyping the password, you can use awk like

openssl pkcs12 -in file.p12 | \
awk '/BEGIN PRIVATE/,/END PRIVATE/{t=t $0 RS;next}1; \
  END{process t as the whole PRIVATE KEY PEM}'

or

openssl pkcs12 -in file.p12 | \
awk '/BEGIN PRIVATE/{f=1;next}/END PRIVATE/{f=0;next}f{t=t $0 RS;next}1; \
    END{process t as just the base64 body}'

Upvotes: 7

Related Questions