Skip to content

Excessive slowness during AES-GCM decryption #56644

Closed

Description

@fabiospampinato

Version

22.2.0

Platform

Darwin MBP.local 21.6.0 Darwin Kernel Version 21.6.0: Wed Aug 10 14:28:23 PDT 2022; root:xnu-8020.141.5~2/RELEASE_ARM64_T6000 arm64

Subsystem

No response

What steps will reproduce the bug?

Profile the following:

import webcrypto from 'tiny-webcrypto';
import encryptor from 'tiny-encryptor';

const SECRET = 'P@ssword!';
const SAMPLE_1GB = new Uint8Array ( 1024 * 1024 * 1024 );
const enc = await encryptor.encrypt ( SAMPLE_1GB, SECRET );
const dec = await encryptor.decrypt ( enc, SECRET );

It should produce a trace like this:

Image

Basically we can see that decryption is way slower than encryption, but how come? There seems to be a useless (arguably) copy of the data buffer when decrypting, but I think it would be totally reasonable to simply delete this copy and getting a subarray instead. It's obvious that you shouldn't be modifying something while its being processed by something else if you don't want problems.

Basically this copy has a tangible cost, obviously, but a very intangible benefit, especially since a "tag" is used, if the underlying buffer gets modified under our nose the decryption should fail, I think.

So basically I think we should switch to a subarray instead of a slice there and get rid of this unnecessary slowness.

How often does it reproduce? Is there a required condition?

Always.

What is the expected behavior? Why is that the expected behavior?

No slowness caused by operations of dubious utility at best.

What do you see instead?

Slowness caused by operations of dubious utility at best.

Additional information

No response

Activity

  1. added
    cryptoIssues and PRs related to the crypto subsystem.
    on Jan 18, 2025
  2. tniessen commented on Feb 26, 2025

    @tniessen
    Member

    I think it would be totally reasonable to simply delete this copy and getting a subarray instead. It's obvious that you shouldn't be modifying something while its being processed by something else if you don't want problems.

    Basically this copy has a tangible cost, obviously, but a very intangible benefit, especially since a "tag" is used, if the underlying buffer gets modified under our nose the decryption should fail, I think.

    @fabiospampinato I might be misunderstanding your suggestion. Not copying the data and being susceptible to asynchronous changes to the underlying ArrayBuffer would be a violation of the Web Crypto API specification, which explicitly requires this step:

    Let data be the result of getting a copy of the bytes held by the data parameter passed to the decrypt() method.

    Of course, this copy necessarily incurs some overhead. (A copy-on-write mechanism within V8 would be a great solution!) That being said, the Web Crypto API generally is not a good choice for decrypting such large AES-GCM messages. Typically, node:crypto's createDecipheriv() will result in much better performance and allows processing data in a streaming manner.

  3. added
    performanceIssues and PRs related to the performance of Node.js.
    on Feb 26, 2025
  4. fabiospampinato commented on Feb 26, 2025

    @fabiospampinato
    Author

    Not copying the data and being susceptible to asynchronous changes to the underlying ArrayBuffer would be a violation of the Web Crypto API specification, which explicitly requires this step:

    Aaaaaah I see 😢 That spec has almost been designed to slow down things as much as possible 🤣

    the Web Crypto API generally is not a good choice for decrypting such large AES-GCM messages

    Right, but I'm looking to perform this stuff in the browser, and WebCrypto is the only native game in town there, unfortunately. I should explore using WASM module for this instead 🤔

  5. tniessen commented on Mar 3, 2025

    @tniessen
    Member

    Unfortunately, neither the Web Crypto API nor WASM will be able to match native performance, and will most likely be much slower than node:crypto. While the Web Crypto API incurs significant overhead due to copying and the use of various Promise objects, WebAssembly cannot (yet) utilize hardware acceleration for AES-GCM, and software implementations of AES-GCM tend to be rather slow.

    On a side note, it is rarely advisable to process messages as large as a gigabyte using a single instance of AES-GCM. Most protocols split large entities into much smaller messages and encrypt/decrypt each message separately. This not only helps with performance but also reduces the risk of leaking unverified plaintexts.

    I am going to close this issue as there have been no suggestions for performance improvements that would comply with the specification. That being said, a copy-on-write mechanism within V8 could improve performance significantly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cryptoIssues and PRs related to the crypto subsystem.performanceIssues and PRs related to the performance of Node.js.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions