ronomon / crypto-async

Fast, reliable cipher, hash and hmac methods executed in Node's threadpool for multi-core throughput.
MIT License
174 stars 15 forks source link

Is there support for elliptic curves? #13

Closed dko-slapdash closed 3 years ago

dko-slapdash commented 4 years ago

Hi.

In Node crypto module, there is a createECDH function which allows to work with ECDH (elliptic curves Diffie-Hellman). It allows to build https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme solutions: https://nodejs.org/api/crypto.html#crypto_class_ecdh

But it's all synchronous and slow.

Any plans of adding support to crypto-async?

jorangreef commented 4 years ago

Thanks @dko-slapdash , there are no plans to add createECDH at present because I want to keep the surface area minimal. Also, running crypto in the threadpool is not always faster because of context switches. It all depends on how many createECDH calls per second you are doing and the latency profile of these compared to the context switch cost. i.e. For large SHA256 one-shot digests it may make sense to use the threadpool, so you can use multiple cores and avoid blocking the event loop. For ECDH, have you benchmarked the latency of a single op?

dko-slapdash commented 4 years ago

I have not, but EC keypair generation + ECDH are extremely expensive operations, probably hundreds times more expensive than SHA256.

dko-slapdash commented 3 years ago

I've just done some ad-hoc measurements in our production machines. EC computeSecret blocks the event loop for 8-20ms typically, and if I need to, say, run 50 of them sequentially (ECDH), it's ~500ms. So having them be done in an async way (in a thread pool) would definitely be a great win.

dko-slapdash commented 3 years ago

What I just noticed is that, if ECDH derivation is done in a separate thread (worker_threads node module), then proxying via the worker thread adds almost no penalty to the pure CPU time consumed in comparison to the sync call - because ECDH is so slow.

So we can consider this feature request solved: ECDH is so slow that wrapping it with worker_threads makes it only ~1% slower. Here are the primitives which are slow (and, when run in a worker_threads worker, stop blocking the event loop of the main thread):

export function generateMsgPublicKeyAndDHSecretSync(
  curveName: string,
  userPublicKey: Buffer
): [Buffer, Buffer] {
  const publicEcdh = crypto.createECDH(curveName);
  const msgPublicKey = publicEcdh.generateKeys();
  const dhSecret = publicEcdh.computeSecret(userPublicKey);
  return [msgPublicKey, dhSecret];
}

export function deriveDHSecretSync(
  curveName: string,
  userPrivateKey: Buffer,
  msgPublicKey: Buffer
): Buffer {
  const privateEcdh = crypto.createECDH(curveName);
  privateEcdh.setPrivateKey(userPrivateKey);
  const dhSecret = privateEcdh.computeSecret(msgPublicKey);
  return dhSecret;
}