如何将 .p12 转换为包含未加密 PKCS#1 私钥块的 .pem?

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

我有一个 Certificates.p12 文件,我希望将其转换为 certificates.pem,其中包含 PKCS#1 格式的未加密私钥。我以前可以通过 运行:

做到这一点
openssl pkcs12 -in Certificates.p12 -out certificates.pem -nodes -clcerts

生成的 certificates.pem 文件有一个 PRIVATE KEY PEM 块,正如预期的那样。但是,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 为:

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
}

我的 certificates.pem 中的坏私钥块没有这个 PKCS#1 结构!相反,它的 ASN.1 结构如下所示:

$ 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...

上面的格式是什么? The documentation for openssl pkcs12 只是含糊地说它的输出是“以 PEM 格式编写的”。我需要更强的保证私钥 PEM 块是 PKCS#1 格式。

奇怪的是 openssl rsa 理解“坏”私钥的奇怪格式,并且可以将其转换为正确的 PKCS#1 结构:

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

虽然 openssl rsa 理解输入文件,但该工具似乎无法告诉我 为什么 ,即输入文件的格式是什么。

openssl pkcs12的输出格式是什么?具体它的私钥块是什么格式呢?如何让 openssl pkcs12 输出正确的 PKCS#1 私钥?

哇,那个库假定任何以 PRIVATE KEY 结尾的 PEM 必须是 PKCS1?那是非常错误的。有 几种 xx PRIVATE KEY 格式,只有 一种 是 PKCS1,见下文。

Meta:我认为这是一个骗局,但我找不到,所以无论如何都要回答。

OpenSSL 支持四种不同的 RSA 私钥 PEM 格式:

  • 'traditional' 或 'legacy' 未加密,这是您想要的 PKCS1 格式 (https://www.rfc-editor.org/rfc/rfc8017#appendix-A.1.2),PEM 类型 RSA PRIVATE KEY(不仅仅是 PRIVATE KEY)

  • 'traditional' 或 'legacy' 使用 OpenSSL(SSLeay 的)自定义方案在 PEM 级别加密,该方案使用非常差(且无法修复)的 PBKDF;这具有相同的 PEM 类型 RSA PRIVATE KEY 但添加了 headers Proc-typeDEK-info

  • PKCS8 standard/generic 未加密 (https://www.rfc-editor.org/rfc/rfc5208#section-5),PEM 类型 PRIVATE KEY;这是一个简单的 ASN.1 包装器,包含算法的标识符(即 RSA 的 OID)加上包含 algorithm-dependent 部分的 OCTET STRING,对于 RSA 是 PKCS1

  • PKCS8 standard/generic 加密 (https://www.rfc-editor.org/rfc/rfc5208#section-6),PEM 类型 ENCRYPTED PRIVATE KEY;这使用通常默认至少为 PBKDF2-SHA1-2048 的算法对 ASN.1 中的 PKCS8 数据进行加密

因为 PKCS8 更灵活,并且是标准的(并且相当常用,例如 Java),并且具有更好的加密性,因此通常是首选;请参阅 the manpage for the PEM_read/write functions for keys and some but not all other things.

的注释部分

所有读取 PEM 私钥的 OpenSSL 函数都可以读取其中的任何一个(必要时提供正确的密码),但它们写入的内容因函数和范围选项而异。如您所见,pkcs12 (import)(当前)写入 PKCS8,但 rsa(始终)写入 traditional/PKCS1.

您的选择是:

  • 使用 rsa(如您所做),或在 1.1.0 pkey -traditional 中转换为传统

  • 在 1.0.0 之前的版本中使用 pkcs12,例如 0.9.8,当它编写传统格式时(对于多种算法,而不仅仅是 RSA)。当然,使用过时且不受支持的版本可能会使您面临缺陷甚至漏洞,这些漏洞已在以后的版本中修复。

  • 从(未加密或解密的)PKCS8 中提取 PKCS1 部分。 OpenSSL 对键使用 DER,这意味着 algorithm-dependent blob,即最后一组中最后一个字段的值,始终是数据的连续最后一部分。例如:

    # 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)]

您可以使用 asn1parse -strparse:

提取 PKCS1 部分
    $ 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-----

或者您可以将 PKCS8 body 转换为二进制文件并丢弃前 22+4=26 个字节(因为 header len hl=4 来自上面的第一个 asn1parse):

    $ 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:如果只读取一次 PKCS12 很重要,例如为了避免重新输入密码,您可以使用 awk like

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

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