Title: crypto: createDiffieHellman(prime, generator) blocks for seconds with standard MODP primes (regression since 24.20.0 and 26.6.0) · Issue #67067 · nodejs/node · GitHub
Open Graph Title: crypto: createDiffieHellman(prime, generator) blocks for seconds with standard MODP primes (regression since 24.20.0 and 26.6.0) · Issue #67067 · nodejs/node
X Title: crypto: createDiffieHellman(prime, generator) blocks for seconds with standard MODP primes (regression since 24.20.0 and 26.6.0) · Issue #67067 · nodejs/node
Description: Version v24.22.1 (also v24.20.0, v24.21.0 and v26.12.1) Platform Linux vm 6.18.44-fc-v165 #1 SMP PREEMPT_DYNAMIC @0 x86_64 x86_64 x86_64 GNU/Linux (4 cores) Subsystem crypto What steps will reproduce the bug? No server is needed. Run thi...
Open Graph Description: Version v24.22.1 (also v24.20.0, v24.21.0 and v26.12.1) Platform Linux vm 6.18.44-fc-v165 #1 SMP PREEMPT_DYNAMIC @0 x86_64 x86_64 x86_64 GNU/Linux (4 cores) Subsystem crypto What steps will reprodu...
X Description: Version v24.22.1 (also v24.20.0, v24.21.0 and v26.12.1) Platform Linux vm 6.18.44-fc-v165 #1 SMP PREEMPT_DYNAMIC @0 x86_64 x86_64 x86_64 GNU/Linux (4 cores) Subsystem crypto What steps will reprodu...
Opengraph URL: https://github.com/nodejs/node/issues/67067
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"crypto: createDiffieHellman(prime, generator) blocks for seconds with standard MODP primes (regression since 24.20.0 and 26.6.0)","articleBody":"### Version\n\nv24.22.1 (also v24.20.0, v24.21.0 and v26.12.1)\n\n### Platform\n\n```text\nLinux vm 6.18.44-fc-v165 #1 SMP PREEMPT_DYNAMIC @0 x86_64 x86_64 x86_64 GNU/Linux (4 cores)\n```\n\n### Subsystem\n\ncrypto\n\n### What steps will reproduce the bug?\n\nNo server is needed. Run this on Node.js 24.22.1 and on 24.19.0:\n\n```js\n'use strict';\nconst crypto = require('node:crypto');\n\nfor (const name of ['modp14', 'modp15', 'modp16', 'modp17', 'modp18']) {\n const prime = crypto.getDiffieHellman(name).getPrime();\n const start = process.hrtime.bigint();\n const dh = crypto.createDiffieHellman(prime, Buffer.from([2]));\n const seconds = Number(process.hrtime.bigint() - start) / 1e9;\n console.log(name, `${prime.length * 8} bits`, `${seconds.toFixed(2)} s`,\n 'verifyError', dh.verifyError);\n}\n```\n\nTo show that the main thread is blocked, add this to the same file before the loop:\n\n```js\nsetInterval(() => {}, 100).unref();\nlet last = Date.now();\nsetInterval(() => {\n const now = Date.now();\n if (now - last > 500) console.log('event loop blocked for', now - last, 'ms');\n last = now;\n}, 100).unref();\n```\n\nReal-world case: the `ssh2` library calls `createDiffieHellman(serverPrime, serverGenerator)` for the `diffie-hellman-group-exchange-*` key exchange. With `ssh2` 1.17.0, `diffie-hellman-group-exchange-sha256` and the `aes256-ctr` cipher, the client asks an OpenSSH server for an 8192-bit group. If the server offers the RFC 3526 groups, the handshake takes 0.3 s on 24.14.1 and 24.19.0. On 24.20.0, 24.22.1 and 26.12.1 the process freezes for 29 to 33 s, and the handshake fails with \"Timed out while waiting for handshake\".\n\n### How often does it reproduce? Is there a required condition?\n\nAlways. Every call with a standard RFC 3526 prime (modp14 to modp18) and generator 2 is slow on 24.20.0 and later, and on 26.6.0 and later. The time grows with the size of the prime. crypto.getDiffieHellman(name) and crypto.createDiffieHellmanGroup(name) are fast on every version.\n\n### What is the expected behavior? Why is that the expected behavior?\n\ncreateDiffieHellman(prime, generator) with a standard group returns in well under 1 ms, and verifyError is 0. This is what Node.js 24.19.0 and earlier, and 22.x, do.\n\nWhy: a standard group needs no primality check. Before commit 0290e0a (\"crypto: split OpenSSL 3, BoringSSL, and legacy backends\", PR #64211), DHPointer::check() called OpenSSL DH_check(). OpenSSL recognizes the named groups and skips the check. The commit message says \"No public crypto or TLS API behavior is intentionally changed.\"\n\n### What do you see instead?\n\nThe call blocks the main thread. Times for `createDiffieHellman(prime, 2)` on a 4-core Linux x64 machine:\n\n| Node.js version | modp14 (2048) | modp15 (3072) | modp16 (4096) | modp17 (6144) | modp18 (8192) |\n| --- | --- | --- | --- | --- | --- |\n| 22.23.4 | 0 ms | 0 ms | 0 ms | not measured | not measured |\n| 24.14.1 to 24.19.0 | 0 ms | 0 ms | 0 ms | 0 ms (24.14.1) | 0 ms (24.14.1) |\n| 24.20.0 | 0.28 s | 1.6 s | 3.9 s | not measured | not measured |\n| 24.21.0 | 0.34 s | 2.0 s | 4.0 s | not measured | not measured |\n| 24.22.1 | 0.3 s | 1.9 s | 3.9 s | 12 s | 30 s |\n| 26.12.1 | 0.28 s | 1.7 s | 4.0 s | not measured | not measured |\n\nOn 24.22.1 the time is almost the same as the sum of two `crypto.checkPrimeSync` calls, one for `p` and one for `(p - 1) / 2`:\n\n| Group | createDiffieHellman | checkPrimeSync(p) + checkPrimeSync((p-1)/2) |\n| --- | --- | --- |\n| modp14 | 0.26 s | 0.27 s |\n| modp15 | 1.63 s | 1.62 s |\n| modp16 | 3.66 s | 3.88 s |\n\nOn 24.19.0 the same `createDiffieHellman` calls take 0.00 s.\n\nAffected versions from the source (not all measured): 24.20.0 and later, 26.6.0 and later, and 27.x (`CheckDhParams` is in the tags and branches). Not affected: 25.x, 23.x, 22.x and 20.x. The `v24.22.2-proposal` and `v26.13.0-proposal` branches have the same code.\n\n### Additional information\n\n## Root cause\n\nThe JS constructor reads `verifyError` at once (`lib/internal/crypto/diffiehellman.js`). The getter runs `DHPointer::check()` (`src/crypto/crypto_dh.cc`, function `Check`).\n\nAfter 0290e0a, the OpenSSL 3 path is built with `OPENSSL_NO_DEPRECATED` and no longer uses `DH`. `DHPointer::check()` calls the new `CheckDhParams()` in `deps/ncrypto/ncrypto.cc`. It has no shortcut for known groups. It calls `BN_check_prime(p)` and, when `q` is not set, `BN_check_prime((p - 1) / 2)`.\n\nThe only shortcut is `if (group_name_ != nullptr)`. `group_name_` is set only by `DHPointer::FromGroup()`, which is why `getDiffieHellman(name)` and `createDiffieHellmanGroup(name)` stay fast. `DHPointer::New(p, g)` never sets it.\n\nLocations in the v24.22.1 tag: `deps/ncrypto/ncrypto.cc:2047` (`CheckDhParams`), `:2121` and `:2127` (the two `BN_check_prime` calls), `:2308` (`DHPointer::check()`), `:2314` (the shortcut), and `src/crypto/crypto_dh.cc:373` and `:379`.\n\nRelated commits: b72c0b9 \"crypto: retain legacy DH validation\" (#64547), be82372 \"crypto: handle DH operation failures\" (#64851), e392e1f \"crypto: fix large DH generator validation\" (#64092).\n\n## Suggested fix\n\n1. Add a known-group shortcut in `DHPointer::check()`. When `p` equals one of the RFC 3526 primes and `g` is 2 and `q` is not set, return `CheckResult::NONE`.\n2. Better: in `DHPointer::New(p, g)`, detect a known group and set `group_name_`. Check the effect on `generateKeys()` first.\n3. Add a regression test: `createDiffieHellman(modp18Prime, 2)` returns fast and `verifyError` is 0.\n\nNon-standard primes (for example generator 5, or primes from the OpenSSH `moduli` file) were already slow on all versions. A shortcut for known groups does not change that.\n\n## Workaround\n\nPreload this with `node -r ./dh-fast-path.js app.js`:\n\n```js\n'use strict';\nconst crypto = require('node:crypto');\nconst known = new Map();\nfor (const name of ['modp14', 'modp15', 'modp16', 'modp17', 'modp18']) {\n known.set(crypto.getDiffieHellman(name).getPrime().toString('hex'), name);\n}\nconst original = crypto.createDiffieHellman;\nconst strip = (b) => {\n let i = 0;\n while (i \u003c b.length - 1 && b[i] === 0) i++;\n return b.subarray(i);\n};\ncrypto.createDiffieHellman = function createDiffieHellman(prime, ...rest) {\n const g = rest[0];\n const isTwo = g === 2 || (Buffer.isBuffer(g) && strip(g).equals(Buffer.from([2])));\n if (rest.length === 1 && Buffer.isBuffer(prime) && isTwo) {\n const name = known.get(strip(prime).toString('hex'));\n if (name) return crypto.getDiffieHellman(name);\n }\n return original.call(this, prime, ...rest);\n};\n```\n\nOther workarounds: stay on 24.19.0 or earlier (or 22.x), or remove `diffie-hellman-group-exchange-*` from the SSH `algorithms.kex` list.","author":{"url":"https://github.com/paulrutter","@type":"Person","name":"paulrutter"},"datePublished":"2026-10-27T20:16:27Z","url":"https://github.com/nodejs/node/issues/67067"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:de480ad0-0e16-d39f-9b19-e91b4934e2af |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 8DB2:2FF8F1:50C8E9:68B994:6AED2616 |
| html-safe-nonce | 5ea1cf0080538e6dbbdaee01475f7217c111a135ec6a815cba3af891e387ed18 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4REIyOjJGRjhGMTo1MEM4RTk6NjhCOTk0OjZBRUQyNjE2IiwidmlzaXRvcl9pZCI6IjMyMzM3NDM5MjA0ODIyOTczNjYiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 944fede0fc2453abbbf7101cc5f59f95110a0d81520fc99520fc2e71ce0dff51 |
| hovercard-subject-tag | issue:6066957800 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/67067/issue_layout |
| twitter:image | https://opengraph.githubassets.com/3ea39b6715e175c663a3d6dd37e983f5a819377adbed99bc5600841987a6577b/nodejs/node/issues/67067 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/3ea39b6715e175c663a3d6dd37e983f5a819377adbed99bc5600841987a6577b/nodejs/node/issues/67067 |
| og:image:alt | Version v24.22.1 (also v24.20.0, v24.21.0 and v26.12.1) Platform Linux vm 6.18.44-fc-v165 #1 SMP PREEMPT_DYNAMIC @0 x86_64 x86_64 x86_64 GNU/Linux (4 cores) Subsystem crypto What steps will reprodu... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | paulrutter |
| hostname | github.com |
| expected-hostname | github.com |
| None | 9d95365dda0576efe9b71f45807595e396bd0ca9286fba5367a05048e6ddf8f2 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 98b41fa4b5635ceb45a4c54320fced73817ff290 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width