简体   繁体   中英

How to convert ECDH keys to PEM format with NodeJS crypto module

I want to have only one pair of keys that I can use for ECDH functions and for other function in node:crypto module.

I know there are two ways how to generate keys with node:crypto module.

One way is to use crypto.generateKeyPairSync . This generates keys in format that is accepted by almost all cryptographic functions in node:crypto module:

const crypto = require('node:crypto')
const {publicKey, privateKey} = crypto.generateKeyPairSync('ec', {
    namedCurve: 'secp224r1'
})

const pubKey = publicKey.export({type: 'spki', format: 'pem'}).toString()
const privKey = privateKey.export({type: 'pkcs8', format: 'pem'}).toString()

This outputs keys in pem format.

But if I want to use ECDH I need to generate keys in the following way:

const crypto = require('node:crypto')
const ecdh = crypto.createECDH('secp224r1')
ecdh.generateKeys()

const rawPublic = ecdh.getPublicKey('base64', 'uncompressed')
const rawPrivate = ecdh.getPrivateKey('base64')

Which generates just the raw keys.

How do I generate PEM from raw key or raw key from PEM so I can use only one set of keys instead of generating new set of keys for ECDH?

The crypto module does not directly support the conversion ASN.1/DER <-> raw. A third party library that supports this is eg eckey-utils .

The conversion from raw to PEM key is possible eg as follows.

const privKey = Buffer.from('765573f9676d39f1256d01f1fb2806d30bbfaab8b04ae745d0a77c03', 'hex');
const pubKey = Buffer.from('04468a685192db85873baa45dbec2bcc8217f5291e09e1b581c7f27f3f5585dc535a13e1862563aeb99de167a49557f1a2d49fee67af017eba', 'hex'); // uncompressed

const ecKeyUtils = require('eckey-utils');
const curveName = 'secp224r1';
const pems = ecKeyUtils.generatePem({curveName, privateKey: privKey, publicKey: pubKey});
const x509Pem = pems.publicKey;
const sec1Pem = pems.privateKey;

Thereby the private key is exported in SEC1 format. If the PKCS#8 format is needed, a conversion with the crypto module is possible:

const crypto = require('crypto')
const pkcs8PemFromSec1 = crypto.createPrivateKey({key: sec1Pem, format: 'pem', type: 'sec1'}).export({type: 'pkcs8', format: 'pem'}).toString();

The reverse is:

const privKey = ecKeyUtils.parsePem(sec1Pem).privateKey;
const pubKeyFromPriv = ecKeyUtils.parsePem(sec1Pem).publicKey;
const pubKey = ecKeyUtils.parsePem(x509Pem).publicKey;

If the private key is in PKCS#8 format, it must be converted to SEC1 format beforehand:

const crypto = require('crypto');
const sec1PemFromPkcs8 = crypto.createPrivateKey({key: pkcs8Pem, format: 'pem', type: 'pkcs8'}).export({type: 'sec1', format: 'pem'});

Note that a trim() is needed here before use in parsePem() to remove the trailing newline ( 0x0a ), which parsePem() does not allow.


Another approach for the conversion of raw to PEM keys is to replace the raw keys embedded in the ASN.1/DER byte sequences, as eg in the following for the conversion of a raw private key into a PKCS#8 key (which also contains the public key) and of a raw public key into an X.509/SPKI key for curve secp224r1:

const privKey = Buffer.from('765573f9676d39f1256d01f1fb2806d30bbfaab8b04ae745d0a77c03', 'hex');
const pubKey = Buffer.from('04468a685192db85873baa45dbec2bcc8217f5291e09e1b581c7f27f3f5585dc535a13e1862563aeb99de167a49557f1a2d49fee67af017eba', 'hex'); // uncompressed

const crypto = require('crypto');
const privA = Buffer.from('3078020100301006072a8648ce3d020106052b810400210461305f020101041c', 'hex');
const privB = Buffer.from('a13c033a00', 'hex');
const pkcs8Der = Buffer.concat([privA, privKey, privB, pubKey]);
const pkcs8 = crypto.createPrivateKey({key: pkcs8Der, format: 'der', type: 'pkcs8'}).export({type: 'pkcs8', format: 'pem'});

const pubA = Buffer.from('304e301006072a8648ce3d020106052b81040021033a00', 'hex');
const x509Der = Buffer.concat([pubA, pubKey]);
const x509 = crypto.createPublicKey({key: x509Der, format: 'der', type: 'spki'}).export({type: 'spki', format: 'pem'});

For the reverse direction, the raw keys can be extracted at their respective positions in the ASN.1/DER encoding.

The advantage of this approach is no dependency, the disadvantage that privA , privB and pubA are ASN.1/DER encodings that contain metadata, such as the curve or length information (as can be seen when examining the PEM keys in an ASN.1 parser, eg https://lapo.it/asn1js/ ), so they are different for each curve.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM