如何将 JWK 转换为 PEM
How to convert JWK to PEM
这个问题更通用,而不是针对特定语言,所以我将解释我的问题以及我在伪代码中尝试过的内容。
我正在尝试从 JWK 集生成 PEM public 密钥。 JWK 包括“e”(指数)和“n”(模数)变量。我的问题是,在不使用任何库且不使用 OpenSSL 命令行工具的情况下,将此 JWK 转换为 PEM 的确切步骤是什么。
JWK 供参考:
{
"kty": "RSA",
"alg": "RS512",
"kid": "26887d3ee3293c526c0e6dd05f122df53aa3f13d7dad06d25e266fa6f51db79fb52422aaf79f121476237e98dcd6640350fee47fec70e783544ec9a36e4605bc",
"use": "sig",
"n": "14m79mVwIE0JxQdKrgXVf7dVcBS90U0TvG7Yf7dG4NJocz1PNUrKrzGhe_FryOe0JahL_sjA2_rKw7NBCpuVx_zSPFRw6kqjewGicjXGus5Fmlf3zDuqwV4BWIFHyQexMPOly0agFfcM0M0MgBULXjINgBs9MwnRv7JVfRoGqXHsNM45djFDd3o4liu4LPlge_DquZUFLNu-BYAyAlWkz0H2TepZhGrN9VEPmxzQkNzXc1R4MpZvbxrRRgaAA2z094ik3hk86JhfyFq-LDcueZhtshmrYZ95LWgMlQ7PixkeK1HkeEYMt20lmNzR8B8KabimYmibxA4Ay9gpRwfp-Q",
"e": "AQAB"
}
我的大部分研究都来自 node-jwk-to-pem 库(可在此处找到:https://github.com/Brightspace/node-jwk-to-pem) and a Whosebug question which uses a JOSE library in PHP (which can be found here: )
根据我通过阅读上面提到的库(加上其他一些没有完全提到分步过程的文章和问题)得出的结论,我发现第一步是转换整数的模数和指数(特别是 BigInt)。这通常会导致以下结果:
n = 27209154847141595039206198313134388207882403216639061643382959557246344691110642716248339592972939239825436848454712213431917464875221502188500634554426063986720722251339335488215426908010590565758292169712942346285217861588132983738839891250753593240057639347814778952277719613395670962156961452389927728643840215833830614315091417876959205843957512422401240879135352731575182574836052718961865690645602829768565458494497550672252951063585023601307115444743487394113997186698238507983094748342588645472362960665610355698438390751920697759620235642103374737421940385132232531739910444003185620313592808726865629407737
e = 65537
使用此信息,我将如何从指数和模数生成 PEM public 密钥?任何伪代码或建议都会令人难以置信!
A RSA public key 由模数 n
和指数 e
定义。
您可以使用 n
和 e
字段直接从 JWK of type RSA
获取该信息:
The "n" (modulus) parameter contains the modulus value for the RSA
public key. It is represented as a Base64urlUInt-encoded value.
The "e" (exponent) parameter contains the exponent value for the RSA
public key. It is represented as a Base64urlUInt-encoded value.
要创建表示此 public 关键信息的 PEM 文件,您基本上需要构建其等效的 ASN.1 DER 编码表示,可能需要一些序言。在其原始形式中,它是这样定义的 RFC3447:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
ASN.1
定义表示任意数据结构的规则和类型,它在密码学中广泛用于此目的。
ASN.1 仅提供语法,但该信息的实际编码是使用不同的 encoding formats,尤其是 DER 执行的。
执行编码的方式非常重要:Let's Encrypt 提供 an excellent article about the subject. The great lapo.it/asn1js site 也提供了一个用于检查 ASN.1 DER 编码信息的绝佳工具。
例如,考虑从下面提到的 SO 问题中提取的以下模数和指数:
SEQUENCE (2 elements)
INTEGER (2048 bit): EB506399F5C612F5A67A09C1192B92FAB53DB28520D859CE0EF6B7D83D40AA1C1DCE2C0720D15A0F531595CAD81BA5D129F91CC6769719F1435872C4BCD0521150A0263B470066489B918BFCA03CE8A0E9FC2C0314C4B096EA30717C03C28CA29E678E63D78ACA1E9A63BDB1261EE7A0B041AB53746D68B57B68BEF37B71382838C95DA8557841A3CA58109F0B4F77A5E929B1A25DC2D6814C55DC0F81CD2F4E5DB95EE70C706FC02C4FCA358EA9A82D8043A47611195580F89458E3DAB5592DEFE06CDE1E516A6C61ED78C13977AE9660A9192CA75CD72967FD3AFAFA1F1A2FF6325A5064D847028F1E6B2329E8572F36E708A549DDA355FC74A32FDD8DBA65
INTEGER (24 bit): 010001
对应的ASN.1 DER编码如下:
30 82 01 0A ;sequence (0x10A bytes long)
02 82 01 01 ;integer (0x101 bytes long)
00 EB506399F5C612F5A67A09C1192B92FAB53DB28520D859CE0EF6B7D83D40AA1C1DCE2C0720D15A0F531595CAD81BA5D129F91CC6769719F1435872C4BCD0521150A0263B470066489B918BFCA03CE8A0E9FC2C0314C4B096EA30717C03C28CA29E678E63D78ACA1E9A63BDB1261EE7A0B041AB53746D68B57B68BEF37B71382838C95DA8557841A3CA58109F0B4F77A5E929B1A25DC2D6814C55DC0F81CD2F4E5DB95EE70C706FC02C4FCA358EA9A82D8043A47611195580F89458E3DAB5592DEFE06CDE1E516A6C61ED78C13977AE9660A9192CA75CD72967FD3AFAFA1F1A2FF6325A5064D847028F1E6B2329E8572F36E708A549DDA355FC74A32FDD8DBA65
02 03 ;integer (3 bytes long)
010001
在 ASN.1 DER 中,每个元素都被编码为 type-length-value 三元组。
类型由标签标识,通常用一个字节编码。
例如,30
表示 SEQUENCE
,而 02
对应 INTEGER
类型。
上述文章提供了 ASN.1 定义的不同类型的列表以及相应的标签。
length 有点棘手:如果关联值长达 127
(0x7F
)字节,则由单个字节指示.如果字节数大于 127
,那么第一个长度字节指定实际定义长度的字节数。请考虑阅读 this article 的 Length
部分,我认为它非常具有解释性。
然后,您包括实际的 值。
在提供的示例中,30 82 01 0A
表示:
30
: A SEQUENCE
.
82
:长度字节。 0x82 & 0x7F -> 0x02
实际值长度用2个字节表示。
01 0A
:实际值长度,266
字节长。
现在,值 02 82 01 01 00 EB506399F5C612F5A67A09C119...
:
02
:一个INTEGER
82
:长度字节。同样,0x82 & 0x7F -> 0x02
。实际值长度以2个字节表示。
01 01
:实际值长度,257
字节长,2048位的模数用第一个字节0x00填充(参见aforementioned article中的Integer padding
部分).
00 EB506399F5C612F5A67A09C119
: 填充模值。
最后,02 03 010001
:
02
: 一个INTEGER
.
03
: 指数值是3字节长。
010001
: 指数值。
这个结果应该被编码为 base64 并包含在特殊的文本分隔符中,例如 -----BEGIN RSA PUBLIC KEY-----
和 -----END RSA PUBLIC KEY-----
。
请注意,您有几个选项可以用 PEM 格式表示密钥,上面描述的 PKCS#1, and the more generic X.509, suitable for the representation of keys in different formats (the one of the form -----BEGIN PUBLIC KEY-----
). Consider read this wonderful SO question 和相关答案,它不仅详细解释了 RSA public关键基础知识,但也有这些不同表示的注意事项。
考虑阅读 this and especially this other SO 问题,我认为它们也可以提供帮助。
这个问题更通用,而不是针对特定语言,所以我将解释我的问题以及我在伪代码中尝试过的内容。
我正在尝试从 JWK 集生成 PEM public 密钥。 JWK 包括“e”(指数)和“n”(模数)变量。我的问题是,在不使用任何库且不使用 OpenSSL 命令行工具的情况下,将此 JWK 转换为 PEM 的确切步骤是什么。
JWK 供参考:
{
"kty": "RSA",
"alg": "RS512",
"kid": "26887d3ee3293c526c0e6dd05f122df53aa3f13d7dad06d25e266fa6f51db79fb52422aaf79f121476237e98dcd6640350fee47fec70e783544ec9a36e4605bc",
"use": "sig",
"n": "14m79mVwIE0JxQdKrgXVf7dVcBS90U0TvG7Yf7dG4NJocz1PNUrKrzGhe_FryOe0JahL_sjA2_rKw7NBCpuVx_zSPFRw6kqjewGicjXGus5Fmlf3zDuqwV4BWIFHyQexMPOly0agFfcM0M0MgBULXjINgBs9MwnRv7JVfRoGqXHsNM45djFDd3o4liu4LPlge_DquZUFLNu-BYAyAlWkz0H2TepZhGrN9VEPmxzQkNzXc1R4MpZvbxrRRgaAA2z094ik3hk86JhfyFq-LDcueZhtshmrYZ95LWgMlQ7PixkeK1HkeEYMt20lmNzR8B8KabimYmibxA4Ay9gpRwfp-Q",
"e": "AQAB"
}
我的大部分研究都来自 node-jwk-to-pem 库(可在此处找到:https://github.com/Brightspace/node-jwk-to-pem) and a Whosebug question which uses a JOSE library in PHP (which can be found here:
根据我通过阅读上面提到的库(加上其他一些没有完全提到分步过程的文章和问题)得出的结论,我发现第一步是转换整数的模数和指数(特别是 BigInt)。这通常会导致以下结果:
n = 27209154847141595039206198313134388207882403216639061643382959557246344691110642716248339592972939239825436848454712213431917464875221502188500634554426063986720722251339335488215426908010590565758292169712942346285217861588132983738839891250753593240057639347814778952277719613395670962156961452389927728643840215833830614315091417876959205843957512422401240879135352731575182574836052718961865690645602829768565458494497550672252951063585023601307115444743487394113997186698238507983094748342588645472362960665610355698438390751920697759620235642103374737421940385132232531739910444003185620313592808726865629407737
e = 65537
使用此信息,我将如何从指数和模数生成 PEM public 密钥?任何伪代码或建议都会令人难以置信!
A RSA public key 由模数 n
和指数 e
定义。
您可以使用 n
和 e
字段直接从 JWK of type RSA
获取该信息:
The "n" (modulus) parameter contains the modulus value for the RSA public key. It is represented as a Base64urlUInt-encoded value.
The "e" (exponent) parameter contains the exponent value for the RSA public key. It is represented as a Base64urlUInt-encoded value.
要创建表示此 public 关键信息的 PEM 文件,您基本上需要构建其等效的 ASN.1 DER 编码表示,可能需要一些序言。在其原始形式中,它是这样定义的 RFC3447:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
ASN.1
定义表示任意数据结构的规则和类型,它在密码学中广泛用于此目的。
ASN.1 仅提供语法,但该信息的实际编码是使用不同的 encoding formats,尤其是 DER 执行的。
执行编码的方式非常重要:Let's Encrypt 提供 an excellent article about the subject. The great lapo.it/asn1js site 也提供了一个用于检查 ASN.1 DER 编码信息的绝佳工具。
例如,考虑从下面提到的 SO 问题中提取的以下模数和指数:
SEQUENCE (2 elements)
INTEGER (2048 bit): EB506399F5C612F5A67A09C1192B92FAB53DB28520D859CE0EF6B7D83D40AA1C1DCE2C0720D15A0F531595CAD81BA5D129F91CC6769719F1435872C4BCD0521150A0263B470066489B918BFCA03CE8A0E9FC2C0314C4B096EA30717C03C28CA29E678E63D78ACA1E9A63BDB1261EE7A0B041AB53746D68B57B68BEF37B71382838C95DA8557841A3CA58109F0B4F77A5E929B1A25DC2D6814C55DC0F81CD2F4E5DB95EE70C706FC02C4FCA358EA9A82D8043A47611195580F89458E3DAB5592DEFE06CDE1E516A6C61ED78C13977AE9660A9192CA75CD72967FD3AFAFA1F1A2FF6325A5064D847028F1E6B2329E8572F36E708A549DDA355FC74A32FDD8DBA65
INTEGER (24 bit): 010001
对应的ASN.1 DER编码如下:
30 82 01 0A ;sequence (0x10A bytes long)
02 82 01 01 ;integer (0x101 bytes long)
00 EB506399F5C612F5A67A09C1192B92FAB53DB28520D859CE0EF6B7D83D40AA1C1DCE2C0720D15A0F531595CAD81BA5D129F91CC6769719F1435872C4BCD0521150A0263B470066489B918BFCA03CE8A0E9FC2C0314C4B096EA30717C03C28CA29E678E63D78ACA1E9A63BDB1261EE7A0B041AB53746D68B57B68BEF37B71382838C95DA8557841A3CA58109F0B4F77A5E929B1A25DC2D6814C55DC0F81CD2F4E5DB95EE70C706FC02C4FCA358EA9A82D8043A47611195580F89458E3DAB5592DEFE06CDE1E516A6C61ED78C13977AE9660A9192CA75CD72967FD3AFAFA1F1A2FF6325A5064D847028F1E6B2329E8572F36E708A549DDA355FC74A32FDD8DBA65
02 03 ;integer (3 bytes long)
010001
在 ASN.1 DER 中,每个元素都被编码为 type-length-value 三元组。
类型由标签标识,通常用一个字节编码。
例如,30
表示 SEQUENCE
,而 02
对应 INTEGER
类型。
上述文章提供了 ASN.1 定义的不同类型的列表以及相应的标签。
length 有点棘手:如果关联值长达 127
(0x7F
)字节,则由单个字节指示.如果字节数大于 127
,那么第一个长度字节指定实际定义长度的字节数。请考虑阅读 this article 的 Length
部分,我认为它非常具有解释性。
然后,您包括实际的 值。
在提供的示例中,30 82 01 0A
表示:
30
: ASEQUENCE
.82
:长度字节。0x82 & 0x7F -> 0x02
实际值长度用2个字节表示。01 0A
:实际值长度,266
字节长。
现在,值 02 82 01 01 00 EB506399F5C612F5A67A09C119...
:
02
:一个INTEGER
82
:长度字节。同样,0x82 & 0x7F -> 0x02
。实际值长度以2个字节表示。01 01
:实际值长度,257
字节长,2048位的模数用第一个字节0x00填充(参见aforementioned article中的Integer padding
部分).00 EB506399F5C612F5A67A09C119
: 填充模值。
最后,02 03 010001
:
02
: 一个INTEGER
.03
: 指数值是3字节长。010001
: 指数值。
这个结果应该被编码为 base64 并包含在特殊的文本分隔符中,例如 -----BEGIN RSA PUBLIC KEY-----
和 -----END RSA PUBLIC KEY-----
。
请注意,您有几个选项可以用 PEM 格式表示密钥,上面描述的 PKCS#1, and the more generic X.509, suitable for the representation of keys in different formats (the one of the form -----BEGIN PUBLIC KEY-----
). Consider read this wonderful SO question 和相关答案,它不仅详细解释了 RSA public关键基础知识,但也有这些不同表示的注意事项。
考虑阅读 this and especially this other SO 问题,我认为它们也可以提供帮助。