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



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

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

生成的certificates.pem文件具有PRIVATE KEYPEM 块,正如预期的那样。但是,我使用的库不理解此 PEM 块,因为它希望它是 PKCS#1 私钥。PKCS#1 私钥的 ASN.1 结构由 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...

以上格式是什么?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 格式:

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

  • 使用
  • OpenSSL(SSLeay)的自定义方案在PEM级别加密的"传统"或"遗留",该方案使用非常差(且无法修复)的PBKDF;它具有相同的PEM类型RSA PRIVATE KEY但添加了标头Proc-typeDEK-info

  • PEM 类型PRIVATE KEY的 PKCS8 标准/通用未加密 (https://www.rfc-editor.org/rfc/rfc5208#section-5);这是一个简单的 ASN.1 包装器,其中包含算法的标识符(即 RSA 的 OID)和一个包含算法相关部分的八位字节字符串,对于 RSA 来说,该部分是 PKCS1

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

因为PKCS8更灵活,并且是标准的(并且相当常用,例如Java),并且具有更好的加密,所以它通常是首选;请参阅手册页的注释部分,了解密钥的PEM_read/写入函数以及某些但不是所有其他功能。

所有读取 PEM 私钥的 OpenSSL 函数都可以读取其中任何一个(必要时给定正确的密码),但它们写入的内容因函数和一定程度的选项而异。正如您所注意到的pkcs12 (import)(当前)写入 PKCS8,但rsa(总是)写入传统/PKCS1。

您的选择是:

  • 使用rsa(就像您所做的那样),或在 1.1.0pkey -traditional中转换为繁体

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

  • (未加密或解密的)PKCS8 中提取 PKCS1 部分。OpenSSL 对键使用 DER,这意味着依赖于算法的 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)]

您可以使用以下方法提取 PKCS1 部件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-----

或者您可以将 PKCS8 主体转换为二进制,然后丢弃前 22+4=26 个字节(因为上面第一个 asn1parse 的标头 len hl=4):

$ 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,例如

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}'

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}'

最新更新