0

I want to achieve the following with the Nodejs crypto module:

  1. I want to sign a message with my private key on a defined EC and have the signature as raw buffer/hex.
  2. I want to have the respective public key as raw buffer/hex.

I can achieve both goals individually, but I can not achieve them together currently and it seems strange, that this is so hard to achieve with the node crypto module.

With the following code it is easy to generate a signature on a curve, but I cannot manage to decode the publicKey:

//Get pub and priv key from curve
const {publicKey, privateKey} = crypto.generateKeyPairSync('ec', {'namedCurve' : 'secp128r1'});
var message = "Hello";
var signer = crypto.createSign('sha256');
signer.update(message);
// Signature as raw hex. That's what I want.
var sigString = signer.sign(privateKey, 'hex'); // Needs a proper KeyObject

And with this code it is easy to extract the public key for a private key from a curve but the key returned by curve.getPrivateKey is not allowed for signing:

refCurve = crypto.createECDH('secp128r1');
//Public key split into x and y components. That's what I want.
refPubKey = {
 x: '0x' + curve.getPublicKey('hex').slice(2,34),
 y: '0x' + curve.getPublicKey('hex').slice(-32)
}

It is not possible for me to achieve both at the same time. The problem is, that the curve object can ONLY export as buffer/hex, while all the signature functions in crypto ONLY accept proper KeyObjects. And at the same time it seems not possible to convert both into each other. The KeyObjects have no functionality to export to buffer and it is not possible to create a KeyObject from the raw key, exported from the curve. What am I missing? I was also trying to set the respective private Key on the curve with curve.setPrivateKey(privateKey) but even this does not work.

Alternatively, I would also be open to use another Node library, but those I found do not seem to support the curve that I want to use (SECP128R1)

For example, this is how easy it is with python and the ecdsa library:

sk = SigningKey.generate(curve=SECP128r1, hashfunc=sha256)
vk = sk.verifying_key
message = b"Hello"
m = sha256(message)
//Signature
signature = sk.sign(message)
//Public Key
[vk.to_string()[:16], vk.to_string()[16:]]

Thankful for any help!

hagen
  • 3
  • 4

1 Answers1

0

You can export the public key with export() in X.509/SPKI format and DER encoded. For a secp128r1 key the last 32 bytes are the concatenation of x and y:

const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', { 'namedCurve': 'secp128r1' });
// ...

var key = publicKey.export({ type: 'spki',  format: 'der' });
var rawX = key.subarray(-32, -16);
var rawY = key.subarray(-16);

console.log(key.toString('hex'));  // e.g. 3036301006072a8648ce3d020106052b8104001c03220004d15885cfb3c75417bbeb95625da313dcb4d27ecb6f89923ae539faa7c09b4797
console.log(rawX.toString('hex')); // e.g. d15885cfb3c75417bbeb95625da313dc
console.log(rawY.toString('hex')); // e.g. b4d27ecb6f89923ae539faa7c09b4797

This can be checked in an ASN.1 parser, e.g. https://lapo.it/asn1js/.

Note that this curve should not be used because of its length according to NIST.


Another convenient option is the export as JWK, because here x and y can be extracted directly (Base64url encoded). However, only the curves supported by JWK can be exported this way, secp128r1 is not one of them.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Thank you so much! That was indeed very helpful. Thanks for the remark with the curve. I have to use it for a certain use case as the other component (hardware) only supports this curve. – hagen Sep 01 '22 at 16:24