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();
}
