如何将 JWK 中的公钥转换为 OpenSSL 的 PEM?

Posted

技术标签:

【中文标题】如何将 JWK 中的公钥转换为 OpenSSL 的 PEM?【英文标题】:How to convert a public key from a JWK into PEM for OpenSSL? 【发布时间】:2017-05-06 23:00:09 【问题描述】:

有来自 RFC 的 RSA 密钥:

https://www.rfc-editor.org/rfc/rfc7516#appendix-A.1

 "kty":"RSA",
  "n":"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW
       cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S
       psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a
       sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS
       tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj
       YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw",
  "e":"AQAB",
  "d":"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N
       WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9
       3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk
       qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl
       t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd
       VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ",
  "p":"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-
       SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lf
       fNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0",
  "q":"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBm
       UDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aX
       IWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc",
  "dp":"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KL
       hMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827
       rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE",
  "dq":"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCj
       ywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDB
       UfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis",
  "qi":"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7
       AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3
       eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY"
 

我试过jwk-to-pem:

https://***.com/a/35995690/4742108

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAoahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E+BVvxkeDNjbC4
he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S
psk/ZkoFnilakGygTwpZ3uesH+PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenS
ZeyaxziK72UwxrrKoExv6kc5twXTq4h+QChLOln0/mtUZwfsRaMStPs6mS6Xrgxn
xbWhojf663tuEQueGC+FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR/MB/4NUJW/T
qOQtwHYbxevoJArm+L5StowjzGy+/bq6GwIDAQAB
-----END RSA PUBLIC KEY-----

还必须将“RSA PUBLIC KEY”替换为“PUBLIC KEY”。

openssl rsa -inform PEM -pubin 命令给出:

unable to load Public Key
139911798556312:error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag:tasn_dec.c:1197:
139911798556312:error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error:tasn_dec.c:374:Type=X509_ALGOR
139911798556312:error:0D08303A:asn1 encoding routines:ASN1_TEMPLATE_NOEXP_D2I:nested asn1 error:tasn_dec.c:697:Field=algor, Type=X509_PUBKEY
139911798556312:error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib:pem_oth.c:83:

如何获得可用的密钥?

【问题讨论】:

为了澄清一件事,JWK 包含私钥。任何时候看到“p”、“q”或“d”,则 RSA 密钥包含私有参数。 在线转换工具:keytool.online openssl 在错误跟踪器中存在添加此类功能的问题github.com/openssl/openssl/issues/8240 所以请投票或贡献 github.com/latchset/jose 一个使用 JWT 的工具,基于 OpenSSL 【参考方案1】:

我开发了一个 php 类,它能够将公钥/私钥从 JWK 转换为 PEM(反之亦然)。

You will find that class here.

基本上,您必须将 Base64UrlSafe 中的每个组件解码为二进制字符串,并根据RFC3447 中描述的 ASN.1 结构组装所有组件。

不过,我建议您使用专用的库/工具来简化您的工作。 使用我的 PHP 库,您的代码将如下所示:

use  Jose\KeyConverter\RSAKey;
$key = new RSAKey([
    "kty" => "RSA",
    "n"   => "oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw",
    "e"   => "AQAB",
    "d"   => "kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ",
    "p"   => "1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lffNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0",
    "q"   => "wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBmUDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aXIWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc",
    "dp"  => "ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KLhMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE",
    "dq"  => "Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCjywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDBUfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis",
    "qi"  => "VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY",
]);
$pem = $key->toPEM();

【讨论】:

【参考方案2】:

因此,您发布的密钥是公钥和公共指数的简单 asn 序列。它看起来像这样:

SEQUENCE ::= 
    n Integer,
    e Integer

OpenSSL 不喜欢这样,因为它缺少其他一些东西,比如 ObjectIdenifier,以便 openssl 知道密钥用于什么算法。

解决此问题的快速方法是同时输入-RSAPublicKey_in 选项,因此完整的命令将如下所示:

openssl rsa -inform pem -in FILEPATH.pem -pubin -pubout -RSAPublicKey_in

并将文件头改回包含“RSA”:

-----BEGIN RSA PUBLIC KEY-----

还有页脚:

-----END RSA PUBLIC KEY-----

这也会将其输出为“普通”公钥格式,其中包括缺少的 ObjectIdentifier。

注意:我不确定-RSAPublicKey_in 的版本要求是什么,但我使用的是 OpenSSL 1.1.0。

【讨论】:

【参考方案3】:

我编写了一个名为lokey 的命令行工具来帮助进行像这样的关键转换。

使用curlgreptr从问题中获取密钥,我们可以使用以下命令将JWK格式的私钥转换为PEM格式的私钥:

$ curl -s https://tools.ietf.org/rfc/rfc7516.txt | grep '"n":"oahUI' -B1 -A28 | tr -d '[:space:]' | lokey to pem

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAoahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E+BVvxkeDN
jbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2K
rf3Spsk/ZkoFnilakGygTwpZ3uesH+PFABNIUYpOiN15dsQRkgr0vEhxN92i2asb
OenSZeyaxziK72UwxrrKoExv6kc5twXTq4h+QChLOln0/mtUZwfsRaMStPs6mS6X
rgxnxbWhojf663tuEQueGC+FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR/MB/4NU
JW/TqOQtwHYbxevoJArm+L5StowjzGy+/bq6GwIDAQABAoIBAQCQt20iPoZsOSz8
CkJJNhC16Vw222UqI7I/Mytcd4j7KTUXv6SkPFjj5Zjk1ZXkqe1oR5dLWPzYTfvn
HGFYwdfK+Nh5w9P1+nBH8z2BXyf0euHZqdOlMP3cO3rbKlbfIOwnMGdOeti7WLBZ
GAEqsRhjjqoBkisDbEDCebZfu4ZHWGCGSoOnRWqPeRtILPVfJ8Kzr8t6EHC3EcjK
HxGKnLjSnbiag4BuDFXDevFP++W3dRV7hY7cmQk7OWlR/4pNUjY+2Cb50BHFMS0T
6J37g4mvSH4r5UWzdVKd1VMjOdLF/KuPwgsvowb9S/xgC7tUgtIHeU5bTzn7ioTc
POCtOODJAoGBANa+dl5OOnPi7HweT+ONqe2rXT1K8UEsuJrYv7WSNzIH9hLhVW1K
Awb2kUPklsZG5JqBMy+yV66EgY0qyt2CEIGHTIMgBD0z417/4SNv9Xk0j1WX8y9J
X3zZ9NyF6lheTAxbqFGBNIc29r4a5Tf6yq23wyMFG06444InkW8AugpdAoGBAMC2
9+ce4ZkFmbFiZgf75XvO4oYGdfUvJQRETiyLGRuGsmVXvR4vYA1QjgZlA3Wg2FU0
jcf/BcsNWGGvVxqxpw3sNNCdvHEVKHgQf4yiNaJmtDq79U/WlyFsSsHeIL8RSJjl
q90ES/ShAUc5NkWrVAZ5g2SUyAH8E7NIOvFGR77XAoGAZK+YwE7diUh0qR1tR7w8
WHtolDx3MZ/OTowiFvgfeQ3SiresXjm9gZ5KLhMXvo+uz+KUJWDxS5pFQ/M0evdo
1dKiRTjVw/x4NyqyXPM5nULPkcpU827rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznj
nd/zVpAmZZq60WPMBMfKcuECgYAOrSB+AnUN0UZeIu9ARme4oQ3RRSawkHGSPJ1z
ePhlh0GIxEDLzrHS8cKPLBwyVhiDjKgLhhlr2E1VyyOoDcV6IvX2uTyDpCfb1O3R
yPyqrIWnP280MFR8ICIlV4RI6MkNk9gd0djy/Vv6j5nZfm28vH5MJ6R/ujHJ4oNS
opaOKwKBgFSDKTGGz3+O3U9cP8w1F3z4prEnoJzDgNc02l+2hpP/Lq2OkfPmVtRc
VOwJVgMi1sm6qurdelRXe4UHdMun5VCuEpTsnQetYMGpzrFxjgDr/NnB6tBN3i+B
67WZFLir5z6zJ7FFBZX1Hcm1c8co8/SRf18mmNeGq3S0o3+C/xTW
-----END RSA PRIVATE KEY-----

lokey 还有一个fetch 命令,可用于从 OpenID 端点获取 JWK 密钥:

$ lokey fetch jwk example.okta.com
$ lokey fetch jwk login.salesforce.com
$ lokey fetch jwk accounts.google.com

然后您可以再次将此输出通过管道传输到 lokey 以获取 PEM:

$ lokey fetch jwk example.okta.com | lokey to pem

【讨论】:

我得到了这个错误 Traceback (最近一次调用最后): File "/usr/local/bin/lokey", line 7, in from lokey import cli File "/Library/Python/ 2.7/site-packages/lokey/__init__.py”,第 9 行,在 中导入 eris 文件“/Library/Python/2.7/site-packages/eris/__init__.py”,第 14 行,在 中pgpy.constants 导入(文件“/Library/Python/2.7/site-packages/pgpy/__init__.py”,第 5 行,在 from .pgp import PGPKey【参考方案4】:

一些将 JWK 转换为 PEM 的 python 代码


import jwt
from cryptography.hazmat.primitives import serialization

def GetClaim(webtoken):
    webkey = 'insert jwk here'
    public_key = jwt.algorithms.RSAAlgorithm.from_jwk(webkey)
    pubk_bytes = public_key.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo)
    claim = jwt.decode(webtoken, pubk_bytes, algorithms=['RS256'])
    return claim

【讨论】:

对某个答案的一些评论,这意味着某个问题,这个问题,被某人回答了。解释你的一些答案。【参考方案5】:

我写了一个Swift library,它能够将公钥/私钥从 JWK 转换为 PEM PKCS#8 编码。

您可以通过以下方式使用它:

import JWKTransform

let key = try RSAKey(jwk: token)
let publicPem = try key.getPublicKey()
let privatePem = try key.getPrivateKey()

关于实际的 JWK,您包含的 RSA 字段的含义如下:

参数n:Base64 URL 编码字符串,表示 RSA 密钥的modulus。 参数e:Base64 URL 编码字符串,表示 RSA 密钥的public exponent。 参数d:Base64 URL 编码字符串,表示 RSA 密钥的private exponent。 参数p:Base64 URL 编码字符串,表示 RSA 密钥的secret prime factor。 参数q:Base64 URL 编码字符串,表示 RSA 密钥的secret prime factor。 参数dp:Base64 URL 编码字符串,表示 RSA 密钥的first factor CRT exponentd mod (p-1) 参数dq:Base64 URL 编码字符串,表示 RSA 密钥的second factor CRT exponentd mod (q-1) 参数qi:Base64 URL 编码字符串,表示RSA 密钥的first CRT coefficientq^-1 mod p

我在每个参数旁边都包含了,它是 OpenSSL 的 RSA 结构中的对应字段。这只是在您想直接处理 OpenSSL 的情况下:-)

另请注意,如果您将使用引用库生成的密钥与 OpenSSL 生成的 RSA 密钥进行比较:

public key: 这个库应该生成 OpenSSL 生成的公钥。

private key: RSA 私钥只需要q,但在提供上述其余值时,RSA 操作通常要快得多。 OpenSSL 生成的 RSA 私钥文件包括这些值。因此,如果未提供所有私有参数,则生成的私有密钥可能与生成的原始 OpenSSL 不完全匹配。

【讨论】:

想添加椭圆曲线 JWK @channel? 我会调查的。

以上是关于如何将 JWK 中的公钥转换为 OpenSSL 的 PEM?的主要内容,如果未能解决你的问题,请参考以下文章

openssl_verify():提供的密钥参数不能被强制转换为 .pem 文件的公钥

如何从 .cer 中提取 RSA 公钥并使用 OpenSSL 将其存储在 .pem 中?

如何使用 openssl 创建公钥和私钥?

sh 使用openssl为JWT JWK(JSON Web Token JSON Web Key)生成ES512和RS256椭圆曲线密钥对

OpenSSL 生成 RSA 公钥时,默认指数为 65535,如何更改?

如何在 Delphi 中将 JSON Web Key 转换为 PEM 格式?