Repository: denoland/deno
Pull request: fix(ext/node): use correct block sizes for HMAC algorithms

The HMAC implementation was hardcoded to use 64-byte blocks for most algorithms and 128-byte blocks only for sha512/sha384. This is incorrect for many algorithms like sha3-* variants and blake2 which have different block sizes.

Add a lookup table with correct block sizes for all supported hash algorithms and throw an error for unsupported algorithms.

Fixes #31765

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


Changed files:
filename: ext/node/polyfills/internal/crypto/hash.ts
patch: @@ -45,6 +45,7 @@ import {
 import LazyTransform from "ext:deno_node/internal/streams/lazy_transform.js";
 import {
   getDefaultEncoding,
+  getHashBlockSize,
   toBuf,
 } from "ext:deno_node/internal/crypto/util.ts";
 import {
@@ -231,7 +232,7 @@ class HmacImpl extends Transform {
 
     const alg = hmac.toLowerCase();
     this.#algorithm = alg;
-    const blockSize = (alg === "sha512" || alg === "sha384") ? 128 : 64;
+    const blockSize = getHashBlockSize(alg);
     const keySize = keyData.length;
 
     let bufKey: Buffer;

filename: ext/node/polyfills/internal/crypto/util.ts
patch: @@ -7,6 +7,7 @@
 import { notImplemented } from "ext:deno_node/_utils.ts";
 import { Buffer } from "node:buffer";
 import {
+  ERR_CRYPTO_INVALID_DIGEST,
   ERR_INVALID_ARG_TYPE,
   hideStackFrames,
 } from "ext:deno_node/internal/errors.ts";
@@ -86,6 +87,33 @@ export function getCiphers(): string[] {
   return supportedCiphers;
 }
 
+const hashBlockSizes: Record<string, number> = {
+  md5: 64,
+  rmd160: 64,
+  ripemd160: 64,
+  sha1: 64,
+  sha224: 64,
+  sha256: 64,
+  sha384: 128,
+  sha512: 128,
+  "sha512-224": 128,
+  "sha512-256": 128,
+  "sha3-224": 144,
+  "sha3-256": 136,
+  "sha3-384": 104,
+  "sha3-512": 72,
+  blake2b512: 128,
+  blake2s256: 64,
+};
+
+export function getHashBlockSize(algorithm: string): number {
+  const blockSize = hashBlockSizes[algorithm];
+  if (blockSize === undefined) {
+    throw new ERR_CRYPTO_INVALID_DIGEST(algorithm);
+  }
+  return blockSize;
+}
+
 export function getCipherInfo(
   nameOrNid: string | number,
   options?: { keyLength?: number; ivLength?: number },
@@ -242,6 +270,7 @@ export default {
   getCiphers,
   getCipherInfo,
   getCurves,
+  getHashBlockSize,
   getOpenSSLSecLevel,
   secureHeapUsed,
   setEngine,

filename: tests/unit_node/crypto/crypto_hash_test.ts
patch: @@ -2,7 +2,7 @@
 import { createHash, createHmac, getHashes, hash } from "node:crypto";
 import { Buffer } from "node:buffer";
 import { Readable } from "node:stream";
-import { assert, assertEquals } from "@std/assert";
+import { assert, assertEquals, assertThrows } from "@std/assert";
 
 // https://github.com/denoland/deno/issues/18140
 Deno.test({
@@ -15,6 +15,26 @@ Deno.test({
   },
 });
 
+Deno.test({
+  name: "[node/crypto] createHmac sha512-224",
+  fn() {
+    assertEquals(
+      createHmac("sha512-224", "secret").update("hello").digest("hex"),
+      "27ade3215d20a0e939a1ff98f91052148e85f2ece87d926d6a2c1aad",
+    );
+  },
+});
+
+Deno.test({
+  name: "[node/crypto] createHmac sha512-256",
+  fn() {
+    assertEquals(
+      createHmac("sha512-256", "secret").update("hello").digest("hex"),
+      "e1a285d0317f7cce89acb5642fb6e82fc16d14ab588b0a5abcc7c20ea748594e",
+    );
+  },
+});
+
 Deno.test({
   name: "[node/crypto] createHash digest",
   fn() {
@@ -159,3 +179,74 @@ Deno.test("[node/crypto.createHmac] should not print deprecation warning", async
   const decodedStderr = new TextDecoder().decode(stderr).trim();
   assertEquals(decodedStderr, "");
 });
+
+Deno.test({
+  name: "[node/crypto] createHmac sha3-224",
+  fn() {
+    assertEquals(
+      createHmac("sha3-224", "secret").update("hello").digest("hex"),
+      "d078791e9bf080c2139f883ac65033d4b5b75bbdb4088c494d0b6a14",
+    );
+  },
+});
+
+Deno.test({
+  name: "[node/crypto] createHmac sha3-256",
+  fn() {
+    assertEquals(
+      createHmac("sha3-256", "secret").update("hello").digest("hex"),
+      "850ae61707b3e60d4e45548c4facfda415d301712641fd11535cf395d9e2d7fe",
+    );
+  },
+});
+
+Deno.test({
+  name: "[node/crypto] createHmac sha3-384",
+  fn() {
+    assertEquals(
+      createHmac("sha3-384", "secret").update("hello").digest("hex"),
+      "e24e0dc664132644a6740071af5a05622edffea8afacf0a4060111961bc9148f23c001b6f7d7e79a44b9896b1f00cd85",
+    );
+  },
+});
+
+Deno.test({
+  name: "[node/crypto] createHmac sha3-512",
+  fn() {
+    assertEquals(
+      createHmac("sha3-512", "secret").update("hello").digest("hex"),
+      "bc07c2dfc0295b420662bda474eb8db11b0389822e13da56cf9991f467f2f6c713c481aa8663900ecaee310bf2f226eaa5c2d1345dfebee990658bd529a9c504",
+    );
+  },
+});
+
+Deno.test({
+  name: "[node/crypto] createHmac blake2b512",
+  fn() {
+    assertEquals(
+      createHmac("blake2b512", "secret").update("hello").digest("hex"),
+      "59d8e60d8f7f54753ab7b823b11f20879c4db732e5b56a0da5559d10b2c2b7ac37d47474b668725b661178359ad71c189597108dd2d94ca051697fbc24b6d7ad",
+    );
+  },
+});
+
+Deno.test({
+  name: "[node/crypto] createHmac blake2s256",
+  fn() {
+    assertEquals(
+      createHmac("blake2s256", "secret").update("hello").digest("hex"),
+      "56f9d5d171c31a9481d1949743ddd370209f7c666ba8bb6872067ad70398d9ce",
+    );
+  },
+});
+
+Deno.test({
+  name: "[node/crypto] createHmac unknown algorithm throws",
+  fn() {
+    assertThrows(
+      () => createHmac("unknown-algorithm", "secret"),
+      TypeError,
+      "Invalid digest: unknown-algorithm",
+    );
+  },
+});