2

Goal:
Generate private / public EC key pair using specified curve.
Public key should be in compressed DER.
Private key should be in DER.

Example:
Private key Base64:

openssl ecparam -name secp256k1 -genkey -noout -out secp256k1-key.pem
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEILmOaO0KmLm5LhlJZOXbcoqALQ4odJ65HtO3HbIvc2jRoAcGBSuBBAAK
oUQDQgAEvXnfhfL2zg4zzQiJoZAtJ5Qm6NkddYjLUnRCxRY/WUWzuN6xTCUacSth
ftrhK43tQA5hmEpk95gqhknHVKktnQ==
-----END EC PRIVATE KEY-----

Public key Base64:

openssl ec -in secp256k1-key.pem -pubout -outform DER -conv_form compressed | base64
MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgADvXnfhfL2zg4zzQiJoZAtJ5Qm6NkddYjLUnRCxRY/WUU=

What I have tried:.

  1. crypto.generateKeyPair: Cannot export public key as compressed DER. No mention how to export private key as DER.
  2. ecdh.generateKeys: Cannot export as DER.
  3. npm ec-key: Does not support compressed export of public key.
jm18457
  • 443
  • 1
  • 7
  • 19

2 Answers2

2

I managed to solve it.

Create schemas for RFC5480 and RFC59159 using ASN1.js

const asn1 = require("asn1.js");

const ASN1ECRfc5915Key = asn1.define("Rfc5915Key", function () {
  this.seq().obj(
    this.key("version").int(),
    this.key("privateKey").octstr(),
    this.key("parameters").optional().explicit(0).objid({
      "1 3 132 0 10": "secp256k1",
    }),
    this.key("publicKey").optional().explicit(1).bitstr()
  );
});

const ASN1ECRfc5280Key = asn1.define("Rfc5280Key", function () {
  const self = this;
  self.seq().obj(
    self
      .key("algorithm")
      .seq()
      .obj(
        this.key("algorithm").objid({
          "1 2 840 10045 2 1": "EC",
        }),
        this.key("parameters").objid({
          "1 3 132 0 10": "secp256k1",
        })
      ),
    self.key("subjectPublicKey").bitstr()
  );
});

Generate private key using crypto.createECDH

function generatePrivateKey(curve) {
  const ecdh = crypto.createECDH(curve);
  ecdh.generateKeys();

  return ASN1ECRfc5915Key.encode(
    {
      version: 1,
      privateKey: ecdh.getPrivateKey(),
      parameters: curve,
      publicKey: {
        data: ecdh.getPublicKey(),
      },
    },
    "der"
  );
}

console.log(generatePrivateKey("secp256k1").toString("base64"))
// MHQCAQEEIAjhVqemJ8iEla6JeA9QhgDttBHgrJLqKgdRgXKaDBx8oAcGBSuBBAAKoUQDQgAEn++0D2O/1vTTurCN6t623tP5LCYqhyKj7Xmv9RwYcGorv/b++vDx92s5lksBYBj7zAoUrsw2R2gXeQiutJskFw==

Get Public key

function getPublicKey(privateKeyInBase64) {
  const key = Buffer.from(privateKeyInBase64, "base64");
  const { privateKey, parameters } = ASN1ECRfc5915Key.decode(key, "der");

  const ecdh = crypto.createECDH(parameters);
  ecdh.setPrivateKey(privateKey);

  const publicKey = ASN1ECRfc5280Key.encode(
    {
      algorithm: {
        algorithm: "EC",
        parameters,
      },
      subjectPublicKey: {
        data: ecdh.getPublicKey(undefined, "compressed"),
      },
    },
    "der"
  );

  return publicKey.toString("base64");
}

console.log(getPublicKey("MHQCAQEEIAjhVqemJ8iEla6JeA9QhgDttBHgrJLqKgdRgXKaDBx8oAcGBSuBBAAKoUQDQgAEn++0D2O/1vTTurCN6t623tP5LCYqhyKj7Xmv9RwYcGorv/b++vDx92s5lksBYBj7zAoUrsw2R2gXeQiutJskFw=="))
// MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgADn++0D2O/1vTTurCN6t623tP5LCYqhyKj7Xmv9RwYcGo=

console.log(getPublicKey("MHQCAQEEILmOaO0KmLm5LhlJZOXbcoqALQ4odJ65HtO3HbIvc2jRoAcGBSuBBAAKoUQDQgAEvXnfhfL2zg4zzQiJoZAtJ5Qm6NkddYjLUnRCxRY/WUWzuN6xTCUacSthftrhK43tQA5hmEpk95gqhknHVKktnQ=="))
// MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgADvXnfhfL2zg4zzQiJoZAtJ5Qm6NkddYjLUnRCxRY/WUU=
jm18457
  • 443
  • 1
  • 7
  • 19
0

I believe the following is a native implementation in node.

Taking inspiration from: How to convert ECDH keys to PEM format with NodeJS crypto module

Here's the updated implementation:

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

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

const privKey = Buffer.from(rawPrivate, 'hex');
const pubKey = Buffer.from(rawPublic, 'hex'); // uncompressed

// Build the private key
const privA = Buffer.from('30740201010420', 'hex');
const privB = Buffer.from('A00706052B8104000AA144034200', 'hex');
const privateKeyDer = Buffer.concat([privA, privKey, privB, pubKey]);
console.log('Private Key ::');
console.log(privateKeyDer.toString('base64'));
// Private Key ::
// MHQCAQEEICG7oP2vJzOrh3k7Q7PjZ5Yy91Kh0l5LldL2sHD57GwBoAcGBSuBBAAKoUQDQgAEgIjM+1h4s2JROafAyiiGlNooHwTBoKzDRVYAOwTNlpCudExqi5MxHXY3hwTuJOPN5rGJyMSZR/epTxQmmvWHCA==

// Build the public key   
const pubA = Buffer.from('3056301006072A8648CE3D020106052B8104000A034200', 'hex');
const publicKeyDer = Buffer.concat([pubA, pubKey]);

const rawCompressedPublic = ecdh.getPublicKey('hex', 'compressed');
const compressedPubKey = Buffer.from(rawCompressedPublic, 'hex'); // compressed
const compressedPubA = Buffer.from('3036301006072A8648CE3D020106052B8104000A032200', 'hex');
const compressedPublicKeyDer = Buffer.concat([compressedPubA, compressedPubKey]);
console.log('Public Key ::');
console.log(publicKeyDer.toString('base64'));

console.log('Compressed Public Key ::');
console.log(compressedPublicKeyDer.toString('base64'));
// openssl pkey -pubout -in privatekey.pem
// Public Key ::
// MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEgIjM+1h4s2JROafAyiiGlNooHwTBoKzDRVYAOwTNlpCudExqi5MxHXY3hwTuJOPN5rGJyMSZR/epTxQmmvWHCA==

// openssl pkey -pubout -ec_conv_form compressed -in privatekey.pem
// Compressed Public Key ::
// MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgACgIjM+1h4s2JROafAyiiGlNooHwTBoKzDRVYAOwTNlpA=
Zane
  • 371
  • 2
  • 9