网站搜索

node.js - 如何在 NodeJS/Typescript 中将 JWK 转换为 PEM


我想从 jwk 端点下载公钥,并将其转换为 pem 格式,这在 jwt 的 sign 函数中是必需的。

export type Secret =
    | string
    | Buffer
    | { key: string | Buffer; passphrase: string };

jwk 格式可以通过 crypto 中的 subtle 作为 JsonWebKey 类型的 webKey 导入,并返回为 CryptoKey

const pubKey = await subtle.importKey(
    "jwk",
    webKey,
    { hash: 'SHA-256', name: 'RSA-OAEP' },
    true,
    []
  );

CryptoKey 可以使用 subtle.exportKey 导出,但结果是 ByteArray,将其转换为 pem 时的方法为:1. 将字节转换为字符,然后 2. 转为 base64 并添加到带有 -----BEGIN PUBLIC KEY----- 的信封中,每 64 个字符换行,这会生成一个 无效密钥。

/**
 * Exports the given key into the specified format, if supported.
 *
 * If the `<CryptoKey>` is not extractable, the returned promise will reject.
 *
 * When `format` is either `'pkcs8'` or `'spki'` and the export is successful,
 * the returned promise will be resolved with an `<ArrayBuffer>` containing the exported key data.
 *
 * When `format` is `'jwk'` and the export is successful, the returned promise will be resolved with a
 * JavaScript object conforming to the {@link https://tools.ietf.org/html/rfc7517 JSON Web Key} specification.
 * @param format Must be one of `'raw'`, `'pkcs8'`, `'spki'`, or `'jwk'`.
 * @returns `<Promise>` containing `<ArrayBuffer>`.
 * @since v15.0.0
 */
exportKey(format: 'jwk', key: CryptoKey): Promise<JsonWebKey>;
exportKey(format: Exclude<KeyFormat, 'jwk'>, key: CryptoKey): Promise<ArrayBuffer>;

此外,公钥只能导出为 spki 和 jwk,不支持 raw。

问题是如何尽可能简单地将 jwk 转换为 pem?!

答案#1

关键是使用 static from(key: webcrypto.CryptoKey): KeyObject; 将 CryptoKey 转换为 KeyObject,然后使用作为 KeyObject 成员的 export(options: KeyExportOptions<'pem'>): string | Buffer;。

该函数将会是

export async function jwkToPem(webKey: JsonWebKey): Promise<string> {
  const cryptoKey = await subtle.importKey(
    "jwk",
    webKey,
    { hash: 'SHA-256', name: 'RSA-OAEP' },
    true,
    []
  );

  return KeyObject.from(cryptoKey).export({ format: "pem", type: "pkcs1"}).toString();
}

Topaco 的建议

使用接受 JsonWebKeyInput 并生成可以轻松导出的 KeyObject 的 createPublicKey 可能会更简单。

    /**
     * Creates and returns a new key object containing a public key. If `key` is a
     * string or `Buffer`, `format` is assumed to be `'pem'`; if `key` is a `KeyObject`with type `'private'`, the public key is derived from the given private key;
     * otherwise, `key` must be an object with the properties described above.
     *
     * If the format is `'pem'`, the `'key'` may also be an X.509 certificate.
     *
     * Because public keys can be derived from private keys, a private key may be
     * passed instead of a public key. In that case, this function behaves as if {@link createPrivateKey} had been called, except that the type of the
     * returned `KeyObject` will be `'public'` and that the private key cannot be
     * extracted from the returned `KeyObject`. Similarly, if a `KeyObject` with type`'private'` is given, a new `KeyObject` with type `'public'` will be returned
     * and it will be impossible to extract the private key from the returned object.
     * @since v11.6.0
     */
    function createPublicKey(key: PublicKeyInput | string | Buffer | KeyObject | JsonWebKeyInput): KeyObject;

然后

export async function jwkToPem(webKey: JsonWebKey): Promise<string> {
  const pubKey: KeyObject = createPublicKey({
    key: webKey,
    format: 'jwk'
  });

  return pubKey.export({ format: "pem", type: "pkcs1"}).toString();
}

相关文章