Commit 38187f81ebc for nodejs

commit 38187f81ebca290af81d560071a2ac0e661e0a39
Author: Filip Skokan <panva.ip@gmail.com>
Date:   Wed Sep 23 13:34:11 2026 +0200

    crypto: use backend cSHAKE and KMAC

    Require cSHAKE and KMAC output lengths and KMAC key lengths to be
    multiples of 8 bits. KMAC keys must be at least 32 bits. Share these
    restrictions between operations and supports.

    Use OpenSSL's KMAC provider for all supported inputs and its cSHAKE
    implementation for non-empty function names or customization strings.
    Keep using SHAKE when both cSHAKE parameters are empty. Remove the
    custom Keccak framing, partial-bit handling, and short-key fallback.

    Document the OpenSSL 4.0 requirement for non-empty cSHAKE parameters and
    reject customization strings containing null bytes. Keep the documented
    512-byte customization limit and let backend failures reach callers.

    Signed-off-by: Filip Skokan <panva.ip@gmail.com>
    Assisted-by: Codex
    PR-URL: https://github.com/nodejs/node/pull/66237
    Reviewed-By: James M Snell <jasnell@gmail.com>
    Reviewed-By: Aviv Keller <me@aviv.sh>

diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md
index f6125581267..609ebf00379 100644
--- a/doc/api/webcrypto.md
+++ b/doc/api/webcrypto.md
@@ -1842,6 +1842,9 @@ changes:
     description: Renamed `cShakeParams.length` to `cShakeParams.outputLength`.
 -->

+When both `functionName` and `customization` are empty or `undefined`, cSHAKE is
+equivalent to plain SHAKE.
+
 #### `cShakeParams.name`

 <!-- YAML
@@ -1858,7 +1861,8 @@ added:
  - v24.15.0
 -->

-* Type: {number} represents the requested output length in bits.
+* Type: {number} represents the requested output length in bits. Must be a
+  multiple of 8.

 #### `cShakeParams.functionName`

@@ -1875,9 +1879,10 @@ changes:
 * Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}

 The `functionName` member represents the NIST function-name byte string used to
-domain-separate functions built on top of cSHAKE. Accepted values are:
+domain-separate functions built on top of cSHAKE. Non-empty values require
+OpenSSL 4.0 or later. Accepted values are:

-* empty or `undefined`, in which case cSHAKE is equivalent to plain SHAKE
+* empty or `undefined`
 * the ASCII byte sequence `'KMAC'`
 * the ASCII byte sequence `'TupleHash'`
 * the ASCII byte sequence `'ParallelHash'`
@@ -1896,11 +1901,11 @@ changes:

 * Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}

-The `customization` member represents the customization data. Accepted
-values are:
+The `customization` member represents the customization data. Non-empty values
+require OpenSSL 4.0 or later. Accepted values are:

-* empty or `undefined`, in which case cSHAKE is equivalent to plain SHAKE
-* up to 512 bytes of arbitrary data
+* empty or `undefined`
+* up to 512 bytes of data without null bytes

 ### Class: `EcdhKeyDeriveParams`

@@ -2360,7 +2365,7 @@ added: v24.8.0
 * Type: {number}

 The optional number of bits in the KMAC key. This is optional and should
-be omitted for most cases.
+be omitted for most cases. The key length must be at least 32 and a multiple of 8.

 #### `kmacImportParams.name`

@@ -2410,7 +2415,8 @@ added: v24.8.0

 The number of bits to generate for the KMAC key. If omitted,
 the length will be determined by the KMAC algorithm used.
-This is optional and should be omitted for most cases.
+This is optional and should be omitted for most cases. Must be at least 32 and a
+multiple of 8.

 #### `kmacKeyGenParams.name`

@@ -2448,7 +2454,8 @@ added:
  - v24.15.0
 -->

-* Type: {number} represents the requested output length in bits.
+* Type: {number} represents the requested output length in bits. Must be a
+  multiple of 8.

 #### `kmacParams.customization`

diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js
index 12588b57263..97bed5aab4a 100644
--- a/lib/internal/crypto/hash.js
+++ b/lib/internal/crypto/hash.js
@@ -5,12 +5,10 @@ const {
   ObjectSetPrototypeOf,
   StringPrototypeToLowerCase,
   Symbol,
-  TypedArrayPrototypeGetBuffer,
   TypedArrayPrototypeIncludes,
 } = primordials;

 const {
-  CShakeJob,
   Hash: _Hash,
   HashJob,
   Hmac: _Hmac,
@@ -23,10 +21,7 @@ const {
 const {
   getStringOption,
   jobPromise,
-  jobPromiseThen,
   normalizeHashName,
-  numBitsToBytes,
-  truncateToBitLength,
   validateAlgorithm,
   validateMaxBufferLength,
   kHandle,
@@ -42,7 +37,6 @@ const {
 } = require('internal/crypto/keys');

 const {
-  lazyDOMException,
   normalizeEncoding,
   encodingsMap,
   getDeprecationWarningEmitter,
@@ -267,30 +261,20 @@ function asyncDigest(algorithm, data) {
       const outputLength = algorithm.outputLength;
       if (getOptionalByteLength(algorithm.functionName) ||
           getOptionalByteLength(algorithm.customization)) {
-        if (CShakeJob === undefined) {
-          throw lazyDOMException(
-            'Non-empty CShakeParams functionName or customization is not supported',
-            'NotSupportedError');
-        }
-
-        return jobPromise(() => new CShakeJob(
+        return jobPromise(() => new HashJob(
           kCryptoJobWebCrypto,
-          algorithm.name,
+          StringPrototypeToLowerCase(algorithm.name),
           data,
+          outputLength,
           algorithm.functionName,
-          algorithm.customization,
-          outputLength));
+          algorithm.customization));
       }

-      const bits = jobPromise(() => new HashJob(
+      return jobPromise(() => new HashJob(
         kCryptoJobWebCrypto,
         normalizeHashName(algorithm.name),
         data,
-        numBitsToBytes(outputLength) * 8));
-      if (outputLength % 8 === 0)
-        return bits;
-      return jobPromiseThen(bits, (bits) =>
-        TypedArrayPrototypeGetBuffer(truncateToBitLength(outputLength, bits)));
+        outputLength));
     }
     case 'TurboSHAKE128':
       // Fall through
diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js
index 742e1ebe590..ed2be2b7fdb 100644
--- a/lib/internal/crypto/mac.js
+++ b/lib/internal/crypto/mac.js
@@ -193,7 +193,6 @@ function kmacSignVerify(key, data, algorithm, signature) {
     getCryptoKeyHandle(key),
     algorithm.name,
     algorithm.customization,
-    getCryptoKeyAlgorithm(key).length,
     algorithm.outputLength,
     data,
     signature));
diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js
index a9ad6dfb62f..d1f1cdc9b4f 100644
--- a/lib/internal/crypto/util.js
+++ b/lib/internal/crypto/util.js
@@ -729,7 +729,7 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
 }

 function validateKmacKeyLength(length) {
-  if ((length < 32 || length % 8) && isFips())
+  if (length < 32 || length % 8 !== 0)
     throw lazyDOMException('Invalid key length', 'NotSupportedError');
 }

diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js
index 6585baa6fc5..7a2ff063fa1 100644
--- a/lib/internal/crypto/webcrypto.js
+++ b/lib/internal/crypto/webcrypto.js
@@ -23,7 +23,6 @@ const {
 } = primordials;

 const {
-  CShakeJob,
   kWebCryptoKeyFormatRaw,
   kWebCryptoKeyFormatPKCS8,
   kWebCryptoKeyFormatSPKI,
@@ -72,7 +71,6 @@ const {
   prepareWebCryptoResult,
   validateAlgorithm,
   validateMaxBufferLength,
-  getOptionalByteLength,
 } = require('internal/crypto/util');

 const {
@@ -1923,15 +1921,7 @@ function check(op, alg, length) {
   }

   switch (op) {
-    case 'digest': {
-      if ((normalizedAlgorithm.name === 'cSHAKE128' ||
-           normalizedAlgorithm.name === 'cSHAKE256') &&
-          (getOptionalByteLength(normalizedAlgorithm.functionName) ||
-           getOptionalByteLength(normalizedAlgorithm.customization))) {
-        return CShakeJob !== undefined;
-      }
-      return true;
-    }
+    case 'digest':
     case 'decapsulate':
     case 'decrypt':
     case 'encapsulate':
diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js
index 4addaf62bcc..33705dbb9c3 100644
--- a/lib/internal/crypto/webidl.js
+++ b/lib/internal/crypto/webidl.js
@@ -11,14 +11,12 @@ const {
   StringPrototypeCharCodeAt,
   StringPrototypeSplit,
   StringPrototypeToLowerCase,
+  TypedArrayPrototypeIncludes,
 } = primordials;

 const {
   lazyDOMException,
 } = require('internal/util');
-const {
-  isUint32,
-} = require('internal/validators');
 const {
   getCryptoKeyAlgorithm,
   getCryptoKeyType,
@@ -29,9 +27,9 @@ const {
   validateMaxBufferLength,
   getBufferSourceByteLength,
   getBufferSourceBytes,
+  getHashes,
   isFips,
   kNamedCurveAliases,
-  numBitsToBytes,
   validateKmacKeyLength,
 } = require('internal/crypto/util');
 const {
@@ -293,20 +291,20 @@ function validateZeroLength(parameterName) {
 }

 function validateCShakeOutputLength(V) {
-  if (!isUint32(numBitsToBytes(V) * 8)) {
+  if (V % 8 !== 0) {
     throw lazyDOMException(
       'Invalid CShakeParams outputLength',
-      'OperationError');
+      'NotSupportedError');
   }
 }

 const kCShakeFunctionNames = ['KMAC', 'TupleHash', 'ParallelHash'];

-function validateCShakeFunctionName(V) {
+function validateCShakeFunctionName(V, dict) {
   const length = getBufferSourceByteLength(V);
   if (length === 0) return;

-  if (!isFips()) {
+  if (ArrayPrototypeIncludes(getHashes(), StringPrototypeToLowerCase(dict.name))) {
     const bytes = getBufferSourceBytes(V);
     for (let i = 0; i < kCShakeFunctionNames.length; i++) {
       const functionName = kCShakeFunctionNames[i];
@@ -325,12 +323,17 @@ function validateCShakeFunctionName(V) {
     'NotSupportedError');
 }

-function validateCShakeCustomization(V) {
-  if (isFips() && getBufferSourceByteLength(V) !== 0)
+function validateCShakeCustomization(V, dict) {
+  if (getBufferSourceByteLength(V) === 0) return;
+  if (!ArrayPrototypeIncludes(getHashes(), StringPrototypeToLowerCase(dict.name)))
     throw lazyDOMException(
       'Unsupported CShakeParams customization',
       'NotSupportedError');
   validateMaxBufferLength(V, 'CShakeParams.customization', 512);
+  if (TypedArrayPrototypeIncludes(getBufferSourceBytes(V), 0))
+    throw lazyDOMException(
+      'Unsupported CShakeParams customization',
+      'NotSupportedError');
 }

 converters.RsaPssParams = createAlgorithmDictionaryConverter(
@@ -790,7 +793,7 @@ converters.KmacParams = createAlgorithmDictionaryConverter(
         converter: (V, opts) =>
           converters['unsigned long'](V, enforceRangeOptions(opts)),
         validator: (V) => {
-          if ((V === 0 || V % 8) && isFips())
+          if (V % 8 !== 0 || (V === 0 && isFips()))
             throw lazyDOMException(
               'Invalid KmacParams outputLength',
               'NotSupportedError');
diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc
index 976c921fee9..24d49a6d377 100644
--- a/src/crypto/crypto_hash.cc
+++ b/src/crypto/crypto_hash.cc
@@ -7,21 +7,15 @@
 #include "threadpoolwork-inl.h"
 #include "v8.h"

-#if OPENSSL_WITH_EVP_MAC
-#include <openssl/core_names.h>
 #include <openssl/evp.h>
-#endif

 #if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK
 #include <openssl/digest.h>
 #endif

 #include <algorithm>
-#include <array>
 #include <climits>
 #include <cstdio>
-#include <limits>
-#include <memory>
 #include <string_view>
 #include <utility>

@@ -588,9 +582,6 @@ void Hash::Initialize(Environment* env, Local<Object> target) {
   SetMethodNoSideEffect(context, target, "oneShotDigest", OneShotDigest);

   HashJob::Initialize(env, target);
-#if OPENSSL_WITH_EVP_MAC
-  CShakeJob::Initialize(env, target);
-#endif
 }

 void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
@@ -602,9 +593,6 @@ void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
   registry->Register(OneShotDigest);

   HashJob::RegisterExternalReferences(registry);
-#if OPENSSL_WITH_EVP_MAC
-  CShakeJob::RegisterExternalReferences(registry);
-#endif
 }

 // new Hash(algorithm, xofLen, algorithmId, algorithmCache[, functionName,
@@ -895,346 +883,5 @@ bool HashTraits::DeriveBits(Environment* env,
   return true;
 }

-#if OPENSSL_WITH_EVP_MAC
-namespace {
-
-static constexpr std::array<unsigned char, 1> kEmptyString = {};
-static constexpr size_t kKeccakKmac128Rate = 168;
-static constexpr size_t kKeccakKmac256Rate = 136;
-static constexpr size_t kMaxCShakeCustomizationSize = 512;
-
-struct EncodedLength {
-  std::array<unsigned char, sizeof(size_t) + 1> data;
-  size_t size;
-};
-
-struct EncodedStringInput {
-  const void* data;
-  size_t byte_length;
-  size_t bit_length;
-};
-
-struct KeccakKmacXof {
-  ncrypto::EVPMDCtxPointer ctx;
-  size_t rate;
-};
-
-size_t EncodedLengthSize(size_t value) {
-  size_t size = 1;
-  size_t remaining = value;
-  while (remaining >>= CHAR_BIT) size++;
-  return size + 1;
-}
-
-bool AddSize(size_t a, size_t b, size_t* out) {
-  if (a > std::numeric_limits<size_t>::max() - b) return false;
-  *out = a + b;
-  return true;
-}
-
-EncodedLength EncodeLength(size_t value, bool left) {
-  const size_t value_size = EncodedLengthSize(value) - 1;
-  EncodedLength encoded = {{}, value_size + 1};
-
-  if (left) encoded.data[0] = static_cast<unsigned char>(value_size);
-  for (size_t n = 0; n < value_size; n++) {
-    const size_t shift = CHAR_BIT * (value_size - n - 1);
-    encoded.data[(left ? 1 : 0) + n] =
-        static_cast<unsigned char>(value >> shift);
-  }
-  if (!left) encoded.data[value_size] = static_cast<unsigned char>(value_size);
-
-  return encoded;
-}
-
-bool DigestUpdate(ncrypto::EVPMDCtxPointer* ctx,
-                  const void* data,
-                  size_t size) {
-  if (size == 0) return true;
-  return ctx->digestUpdate(ncrypto::Buffer<const void>{
-      .data = data,
-      .len = size,
-  });
-}
-
-bool DigestUpdateZeros(ncrypto::EVPMDCtxPointer* ctx, size_t size) {
-  static constexpr std::array<unsigned char, 168> zeros = {};
-  while (size > 0) {
-    const size_t chunk = std::min(size, zeros.size());
-    if (!DigestUpdate(ctx, zeros.data(), chunk)) return false;
-    size -= chunk;
-  }
-  return true;
-}
-
-bool EncodedStringSize(size_t byte_length, size_t bit_length, size_t* size) {
-  return AddSize(EncodedLengthSize(bit_length), byte_length, size);
-}
-
-bool ByteLengthToBitLength(size_t byte_length, size_t* bit_length) {
-  if (byte_length > std::numeric_limits<size_t>::max() / CHAR_BIT) {
-    return false;
-  }
-  *bit_length = byte_length * CHAR_BIT;
-  return true;
-}
-
-KeccakKmacXof NewKeccakKmacXof(bool use_128_bits) {
-  // OpenSSL 3.x exposes the cSHAKE/KMAC suffix primitive as KECCAK-KMAC-*.
-  const char* digest_name = use_128_bits ? OSSL_DIGEST_NAME_KECCAK_KMAC128
-                                         : OSSL_DIGEST_NAME_KECCAK_KMAC256;
-  auto digest = std::unique_ptr<EVP_MD, decltype(&EVP_MD_free)>{
-      EVP_MD_fetch(nullptr, digest_name, nullptr), EVP_MD_free};
-  if (!digest) return {};
-
-  auto ctx = ncrypto::EVPMDCtxPointer::New();
-  if (!ctx.digestInit(digest.get())) return {};
-
-  return {
-      .ctx = std::move(ctx),
-      .rate = use_128_bits ? kKeccakKmac128Rate : kKeccakKmac256Rate,
-  };
-}
-
-bool ToEncodedStringInput(const void* data,
-                          size_t byte_length,
-                          EncodedStringInput* input) {
-  if (byte_length > 0 && data == nullptr) return false;
-
-  size_t bit_length;
-  if (!ByteLengthToBitLength(byte_length, &bit_length)) return false;
-
-  *input = {
-      .data = byte_length == 0 ? kEmptyString.data() : data,
-      .byte_length = byte_length,
-      .bit_length = bit_length,
-  };
-  return true;
-}
-
-bool DigestUpdateEncodedLength(ncrypto::EVPMDCtxPointer* ctx,
-                               size_t value,
-                               bool left) {
-  const EncodedLength encoded = EncodeLength(value, left);
-  return DigestUpdate(ctx, encoded.data.data(), encoded.size);
-}
-
-bool DigestUpdateEncodedString(ncrypto::EVPMDCtxPointer* ctx,
-                               const void* data,
-                               size_t byte_length,
-                               size_t bit_length) {
-  return DigestUpdateEncodedLength(ctx, bit_length, true) &&
-         DigestUpdate(ctx, data, byte_length);
-}
-
-bool DigestUpdateBytepad(ncrypto::EVPMDCtxPointer* ctx,
-                         size_t width,
-                         const void* data,
-                         size_t byte_length,
-                         size_t bit_length,
-                         const void* data2 = nullptr,
-                         size_t byte_length2 = 0,
-                         size_t bit_length2 = 0) {
-  if (width == 0) return false;
-
-  size_t encoded_size;
-  size_t written = EncodedLengthSize(width);
-  if (!EncodedStringSize(byte_length, bit_length, &encoded_size) ||
-      !AddSize(written, encoded_size, &written)) {
-    return false;
-  }
-  if (data2 != nullptr) {
-    if (!EncodedStringSize(byte_length2, bit_length2, &encoded_size) ||
-        !AddSize(written, encoded_size, &written)) {
-      return false;
-    }
-  }
-
-  size_t padded_size;
-  if (!AddSize(written, width - 1, &padded_size)) return false;
-  padded_size = padded_size / width * width;
-  DCHECK_GE(padded_size, written);
-  const size_t padding = padded_size - written;
-
-  return DigestUpdateEncodedLength(ctx, width, true) &&
-         DigestUpdateEncodedString(ctx, data, byte_length, bit_length) &&
-         (data2 == nullptr ||
-          DigestUpdateEncodedString(ctx, data2, byte_length2, bit_length2)) &&
-         DigestUpdateZeros(ctx, padding);
-}
-
-}  // namespace
-
-CShakeConfig::CShakeConfig(CShakeConfig&& other) noexcept
-    : in(std::move(other.in)),
-      function_name(std::move(other.function_name)),
-      customization(std::move(other.customization)),
-      variant(other.variant),
-      length(other.length) {}
-
-CShakeConfig& CShakeConfig::operator=(CShakeConfig&& other) noexcept {
-  if (&other == this) return *this;
-  this->~CShakeConfig();
-  return *new (this) CShakeConfig(std::move(other));
-}
-
-void CShakeConfig::MemoryInfo(MemoryTracker* tracker) const {
-  tracker->TraitTrackInline(in, "in");
-  tracker->TraitTrackInline(function_name, "function_name");
-  tracker->TraitTrackInline(customization, "customization");
-}
-
-MaybeLocal<Value> CShakeTraits::EncodeOutput(Environment* env,
-                                             const CShakeConfig& params,
-                                             ByteSource* out) {
-  return out->ToArrayBuffer(env);
-}
-
-Maybe<void> CShakeTraits::AdditionalConfig(
-    CryptoJobMode mode,
-    const FunctionCallbackInfo<Value>& args,
-    unsigned int offset,
-    CShakeConfig* params) {
-  Environment* env = Environment::GetCurrent(args);
-
-  if (IsFipsEnabled()) {
-    THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
-    return Nothing<void>();
-  }
-
-  CHECK(args[offset]->IsString());  // Algorithm name
-  Utf8Value algorithm_name(env->isolate(), args[offset]);
-  std::string_view algorithm_str = algorithm_name.ToStringView();
-
-  if (algorithm_str == "cSHAKE128") {
-    params->variant = CShakeVariant::CSHAKE128;
-  } else if (algorithm_str == "cSHAKE256") {
-    params->variant = CShakeVariant::CSHAKE256;
-  } else {
-    UNREACHABLE();
-  }
-
-  ArrayBufferOrViewContents<char> data(args[offset + 1]);
-  if (!data.CheckSizeInt32()) [[unlikely]] {
-    THROW_ERR_OUT_OF_RANGE(env, "data is too big");
-    return Nothing<void>();
-  }
-  params->in = IsCryptoJobAsync(mode) ? data.ToCopy() : data.ToByteSource();
-
-  if (!args[offset + 2]->IsUndefined()) {
-    ArrayBufferOrViewContents<char> function_name(args[offset + 2]);
-    if (!function_name.CheckSizeInt32()) [[unlikely]] {
-      THROW_ERR_OUT_OF_RANGE(env, "functionName is too big");
-      return Nothing<void>();
-    }
-    params->function_name = IsCryptoJobAsync(mode)
-                                ? function_name.ToCopy()
-                                : function_name.ToByteSource();
-  }
-
-  if (!args[offset + 3]->IsUndefined()) {
-    ArrayBufferOrViewContents<char> customization(args[offset + 3]);
-    if (!customization.CheckSizeInt32()) [[unlikely]] {
-      THROW_ERR_OUT_OF_RANGE(env, "customization is too big");
-      return Nothing<void>();
-    }
-    params->customization = IsCryptoJobAsync(mode)
-                                ? customization.ToCopy()
-                                : customization.ToByteSource();
-  }
-
-  CHECK(args[offset + 4]->IsUint32());  // Length
-  params->length = args[offset + 4].As<Uint32>()->Value();
-
-  return JustVoid();
-}
-
-bool CShakeTraits::DeriveBits(Environment* env,
-                              const CShakeConfig& params,
-                              ByteSource* out,
-                              CryptoJobMode mode,
-                              CryptoErrorStore*) {
-  CShakeParams cshake_params = {
-      .variant = params.variant,
-      .function_name_data = params.function_name.data(),
-      .function_name_size = params.function_name.size(),
-      .customization_data = params.customization.data(),
-      .customization_size = params.customization.size(),
-      .bytepad_input = nullptr,
-      .input_data = params.in.data(),
-      .input_size = params.in.size(),
-      .append_output_length = false,
-      .length = params.length,
-  };
-  return DeriveCShakeBits(cshake_params, out);
-}
-
-bool DeriveCShakeBits(const CShakeParams& params, ByteSource* out) {
-  if (params.customization_size > kMaxCShakeCustomizationSize) {
-    return false;
-  }
-
-  if (params.length == 0) {
-    *out = ByteSource();
-    return true;
-  }
-
-  auto xof = NewKeccakKmacXof(params.variant == CShakeVariant::CSHAKE128);
-  if (!xof.ctx) return false;
-  auto ctx = std::move(xof.ctx);
-
-  EncodedStringInput function_name;
-  EncodedStringInput customization;
-  if (!ToEncodedStringInput(params.function_name_data,
-                            params.function_name_size,
-                            &function_name) ||
-      !ToEncodedStringInput(params.customization_data,
-                            params.customization_size,
-                            &customization)) {
-    return false;
-  }
-
-  if (!DigestUpdateBytepad(&ctx,
-                           xof.rate,
-                           function_name.data,
-                           function_name.byte_length,
-                           function_name.bit_length,
-                           customization.data,
-                           customization.byte_length,
-                           customization.bit_length)) {
-    return false;
-  }
-
-  if (params.bytepad_input != nullptr &&
-      !DigestUpdateBytepad(&ctx,
-                           xof.rate,
-                           params.bytepad_input->data,
-                           params.bytepad_input->byte_length,
-                           params.bytepad_input->bit_length)) {
-    return false;
-  }
-
-  if (!DigestUpdate(&ctx, params.input_data, params.input_size)) {
-    return false;
-  }
-
-  if (params.append_output_length &&
-      !DigestUpdateEncodedLength(&ctx, params.length, false)) {
-    return false;
-  }
-
-  const size_t length_bytes =
-      NumBitsToBytes(static_cast<size_t>(params.length));
-  auto data = ctx.digestFinal(length_bytes);
-  if (!data) [[unlikely]]
-    return false;
-
-  DCHECK(!data.isSecure());
-  *out = ByteSource::Allocated(data.release());
-  if (params.length % CHAR_BIT != 0) TruncateToBitLength(params.length, out);
-  return true;
-}
-#endif  // OPENSSL_WITH_EVP_MAC
-
 }  // namespace crypto
 }  // namespace node
diff --git a/src/crypto/crypto_hash.h b/src/crypto/crypto_hash.h
index 146e9cf55b5..1cdfa2054e4 100644
--- a/src/crypto/crypto_hash.h
+++ b/src/crypto/crypto_hash.h
@@ -86,74 +86,6 @@ struct HashTraits final {

 using HashJob = DeriveBitsJob<HashTraits>;

-#if OPENSSL_WITH_EVP_MAC
-enum class CShakeVariant { CSHAKE128, CSHAKE256 };
-
-struct CShakeBytepadInput final {
-  const void* data;
-  size_t byte_length;
-  size_t bit_length;
-};
-
-struct CShakeParams final {
-  CShakeVariant variant;
-  const void* function_name_data;
-  size_t function_name_size;
-  const void* customization_data;
-  size_t customization_size;
-  const CShakeBytepadInput* bytepad_input;
-  const void* input_data;
-  size_t input_size;
-  bool append_output_length;
-  uint32_t length;  // Output length in bits
-};
-
-bool DeriveCShakeBits(const CShakeParams& params, ByteSource* out);
-
-struct CShakeConfig final : public MemoryRetainer {
-  ByteSource in;
-  ByteSource function_name;
-  ByteSource customization;
-  CShakeVariant variant;
-  uint32_t length;  // Output length in bits
-
-  CShakeConfig() = default;
-
-  explicit CShakeConfig(CShakeConfig&& other) noexcept;
-
-  CShakeConfig& operator=(CShakeConfig&& other) noexcept;
-
-  void MemoryInfo(MemoryTracker* tracker) const override;
-  SET_MEMORY_INFO_NAME(CShakeConfig)
-  SET_SELF_SIZE(CShakeConfig)
-};
-
-struct CShakeTraits final {
-  using AdditionalParameters = CShakeConfig;
-  static constexpr const char* JobName = "CShakeJob";
-  static constexpr AsyncWrap::ProviderType Provider =
-      AsyncWrap::PROVIDER_HASHREQUEST;
-
-  static v8::Maybe<void> AdditionalConfig(
-      CryptoJobMode mode,
-      const v8::FunctionCallbackInfo<v8::Value>& args,
-      unsigned int offset,
-      CShakeConfig* params);
-
-  static bool DeriveBits(Environment* env,
-                         const CShakeConfig& params,
-                         ByteSource* out,
-                         CryptoJobMode mode,
-                         CryptoErrorStore* errors);
-
-  static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
-                                                const CShakeConfig& params,
-                                                ByteSource* out);
-};
-
-using CShakeJob = DeriveBitsJob<CShakeTraits>;
-#endif  // OPENSSL_WITH_EVP_MAC
-
 }  // namespace crypto
 }  // namespace node

diff --git a/src/crypto/crypto_kmac.cc b/src/crypto/crypto_kmac.cc
index 7bdbece9627..f7a994b9e12 100644
--- a/src/crypto/crypto_kmac.cc
+++ b/src/crypto/crypto_kmac.cc
@@ -1,14 +1,11 @@
 #include "crypto/crypto_kmac.h"
 #include "async_wrap-inl.h"
-#include "crypto/crypto_hash.h"
 #include "node_internals.h"
 #include "threadpoolwork-inl.h"

 #if OPENSSL_WITH_EVP_MAC
 #include <openssl/core_names.h>
 #include <openssl/params.h>
-#include <array>
-#include <limits>
 #include <utility>
 #include "crypto/crypto_keys.h"
 #include "crypto/crypto_sig.h"
@@ -26,7 +23,6 @@ using v8::Local;
 using v8::Maybe;
 using v8::MaybeLocal;
 using v8::Nothing;
-using v8::Number;
 using v8::Object;
 using v8::Uint32;
 using v8::Value;
@@ -38,7 +34,6 @@ KmacConfig::KmacConfig(KmacConfig&& other) noexcept
       signature(std::move(other.signature)),
       customization(std::move(other.customization)),
       variant(other.variant),
-      key_length(other.key_length),
       length(other.length) {}

 KmacConfig& KmacConfig::operator=(KmacConfig&& other) noexcept {
@@ -96,27 +91,18 @@ Maybe<void> KmacTraits::AdditionalConfig(
   }
   // If undefined, params->customization remains uninitialized (size 0).

-  CHECK(args[offset + 4]->IsNumber());  // Key length
-  double key_length = args[offset + 4].As<Number>()->Value();
-  if (!(key_length >= 0) ||
-      key_length > static_cast<double>(std::numeric_limits<size_t>::max())) {
-    THROW_ERR_OUT_OF_RANGE(env, "key length is too big");
-    return Nothing<void>();
-  }
-  params->key_length = static_cast<size_t>(key_length);
+  CHECK(args[offset + 4]->IsUint32());  // Length
+  params->length = args[offset + 4].As<Uint32>()->Value();

-  CHECK(args[offset + 5]->IsUint32());  // Length
-  params->length = args[offset + 5].As<Uint32>()->Value();
-
-  ArrayBufferOrViewContents<char> data(args[offset + 6]);
+  ArrayBufferOrViewContents<char> data(args[offset + 5]);
   if (!data.CheckSizeInt32()) [[unlikely]] {
     THROW_ERR_OUT_OF_RANGE(env, "data is too big");
     return Nothing<void>();
   }
   params->data = IsCryptoJobAsync(mode) ? data.ToCopy() : data.ToByteSource();

-  if (!args[offset + 7]->IsUndefined()) {
-    ArrayBufferOrViewContents<char> signature(args[offset + 7]);
+  if (!args[offset + 6]->IsUndefined()) {
+    ArrayBufferOrViewContents<char> signature(args[offset + 6]);
     if (!signature.CheckSizeInt32()) [[unlikely]] {
       THROW_ERR_OUT_OF_RANGE(env, "signature is too big");
       return Nothing<void>();
@@ -128,86 +114,18 @@ Maybe<void> KmacTraits::AdditionalConfig(
   return JustVoid();
 }

-namespace {
-
-static constexpr std::array<unsigned char, 4> kKmacFunctionName = {
-    'K', 'M', 'A', 'C'};
-static constexpr size_t kKmacMinOpenSSLKeySize = 4;
-// Keep the bit-aware path within OpenSSL's KMAC provider limits.
-static constexpr size_t kKmacMaxOpenSSLKeySize = 512;
-static constexpr size_t kKmacMaxOpenSSLCustomizationSize = 512;
-static constexpr size_t kKmacMaxOpenSSLOutputSize = 0xffffff / CHAR_BIT;
-
-bool KmacParamsWithinOpenSSLLimits(const KmacConfig& params,
-                                   size_t key_size,
-                                   size_t length_bytes) {
-  return key_size <= kKmacMaxOpenSSLKeySize &&
-         NumBitsToBytes(params.key_length) <= kKmacMaxOpenSSLKeySize &&
-         params.customization.size() <= kKmacMaxOpenSSLCustomizationSize &&
-         length_bytes <= kKmacMaxOpenSSLOutputSize;
-}
-
-bool DeriveBitsWithCShake(const KmacConfig& params,
-                          const void* key_data,
-                          size_t key_size,
-                          ByteSource* out) {
-  if (IsFipsEnabled()) return false;
-
-  const size_t key_length_bytes = NumBitsToBytes(params.key_length);
-  if (key_size < key_length_bytes) return false;
-
-  CShakeBytepadInput key_input = {
-      .data = key_data,
-      .byte_length = key_length_bytes,
-      .bit_length = params.key_length,
-  };
-  CShakeParams cshake_params = {
-      .variant = params.variant == KmacVariant::KMAC128
-                     ? CShakeVariant::CSHAKE128
-                     : CShakeVariant::CSHAKE256,
-      .function_name_data = kKmacFunctionName.data(),
-      .function_name_size = kKmacFunctionName.size(),
-      .customization_data = params.customization.data(),
-      .customization_size = params.customization.size(),
-      .bytepad_input = &key_input,
-      .input_data = params.data.data(),
-      .input_size = params.data.size(),
-      .append_output_length = true,
-      .length = params.length,
-  };
-  return DeriveCShakeBits(cshake_params, out);
-}
-
-}  // namespace
-
 bool KmacTraits::DeriveBits(Environment* env,
                             const KmacConfig& params,
                             ByteSource* out,
                             CryptoJobMode mode,
                             CryptoErrorStore*) {
-  const bool truncate_to_bit_length = params.length % CHAR_BIT != 0;
-  const size_t length_bytes =
-      NumBitsToBytes(static_cast<size_t>(params.length));
+  if (params.length % CHAR_BIT != 0) return false;
+  const size_t length_bytes = params.length / CHAR_BIT;

   // Get the key data.
   const void* key_data = params.key.GetSymmetricKey();
   size_t key_size = params.key.GetSymmetricKeySize();

-  if (!KmacParamsWithinOpenSSLLimits(params, key_size, length_bytes)) {
-    return false;
-  }
-
-  if (params.length == 0) {
-    *out = ByteSource();
-    return true;
-  }
-
-  // OpenSSL's EVP_MAC provider rejects KMAC keys shorter than 4 bytes.
-  if (params.length % CHAR_BIT != 0 || params.key_length % CHAR_BIT != 0 ||
-      key_size < kKmacMinOpenSSLKeySize) {
-    return DeriveBitsWithCShake(params, key_data, key_size, out);
-  }
-
   // Fetch the KMAC algorithm
   auto mac = EVPMacPointer::Fetch((params.variant == KmacVariant::KMAC128)
                                       ? OSSL_MAC_NAME_KMAC128
@@ -261,7 +179,6 @@ bool KmacTraits::DeriveBits(Environment* env,

   auto buffer = result.release();
   *out = ByteSource::Allocated(buffer.data, buffer.len);
-  if (truncate_to_bit_length) TruncateToBitLength(params.length, out);
   return true;
 }

diff --git a/src/crypto/crypto_kmac.h b/src/crypto/crypto_kmac.h
index 703b03c2c88..4f8fe27d2ff 100644
--- a/src/crypto/crypto_kmac.h
+++ b/src/crypto/crypto_kmac.h
@@ -21,8 +21,7 @@ struct KmacConfig final : public MemoryRetainer {
   ByteSource signature;
   ByteSource customization;
   KmacVariant variant;
-  size_t key_length;  // Key length in bits
-  uint32_t length;    // Output length in bits
+  uint32_t length;  // Output length in bits

   KmacConfig() = default;

diff --git a/test/fixtures/crypto/kmac.js b/test/fixtures/crypto/kmac.js
index ed265bb9c9f..cc1870af2bb 100644
--- a/test/fixtures/crypto/kmac.js
+++ b/test/fixtures/crypto/kmac.js
@@ -114,61 +114,6 @@ module.exports = function() {
         0x76, 0xfc, 0x89, 0x65,
       ]),
     },
-    {
-      // KMAC128 with a short key, generated with OpenSSL's KECCAK-KMAC128
-      // digest over independently encoded NIST SP 800-185 framing.
-      algorithm: 'KMAC128',
-      key: Buffer.from([0x00, 0x01, 0x02]),
-      data: Buffer.from([0x01, 0x02, 0x03]),
-      customization: Buffer.from('Node.js'),
-      outputLength: 256,
-      expected: Buffer.from([
-        0xfb, 0x7c, 0xbb, 0xa2, 0xa1, 0x0d, 0x1a, 0x87, 0x9a, 0x9f, 0x96, 0x8c,
-        0x58, 0x9d, 0x2a, 0xfe, 0x4a, 0x9b, 0xbf, 0x03, 0x7e, 0x85, 0x2c, 0xac,
-        0x05, 0xdd, 0x78, 0x5b, 0x78, 0xd6, 0x57, 0x1c,
-      ]),
-    },
-    {
-      // KMAC256 with a short key, generated with OpenSSL's KECCAK-KMAC256
-      // digest over independently encoded NIST SP 800-185 framing.
-      algorithm: 'KMAC256',
-      key: Buffer.from([0x00, 0x01, 0x02]),
-      data: Buffer.from([0x01, 0x02, 0x03]),
-      customization: Buffer.from('Node.js'),
-      outputLength: 512,
-      expected: Buffer.from([
-        0x2c, 0xcf, 0x20, 0xde, 0xd8, 0xc9, 0x6d, 0xb0, 0x5f, 0x15, 0xe0, 0xb3,
-        0xce, 0x5d, 0xf0, 0x45, 0xc7, 0xd7, 0x4e, 0xfe, 0x18, 0xee, 0x36, 0xa8,
-        0xe4, 0x9a, 0x37, 0xfb, 0xc2, 0xb1, 0xbb, 0xfd, 0xad, 0xf5, 0xb7, 0x89,
-        0xd3, 0xa4, 0xbc, 0xb3, 0xa8, 0x28, 0x8e, 0x9f, 0x25, 0xe6, 0x8d, 0x5b,
-        0x4a, 0x01, 0x0b, 0x90, 0xae, 0x6d, 0x2b, 0xfc, 0xf1, 0xb6, 0xbb, 0x82,
-        0x34, 0x8b, 0x51, 0xd9,
-      ]),
-    },
-    {
-      // KMAC128 with a non-byte-aligned output length. The second byte has its
-      // unused low bits cleared after squeezing a 9-bit result.
-      algorithm: 'KMAC128',
-      key: Buffer.from([0x00, 0x01, 0x02, 0x03]),
-      data: Buffer.from([0x01, 0x02, 0x03]),
-      customization: undefined,
-      outputLength: 9,
-      expected: Buffer.from([0x63, 0x80]),
-    },
-    {
-      // KMAC128 with a non-byte-aligned key length. The raw key is already
-      // truncated to 25 bits, matching WebCrypto import semantics.
-      algorithm: 'KMAC128',
-      key: Buffer.from([0xff, 0xff, 0xff, 0x80]),
-      keyLength: 25,
-      data: Buffer.from([0x01, 0x02, 0x03]),
-      customization: undefined,
-      outputLength: 128,
-      expected: Buffer.from([
-        0x25, 0xea, 0xc7, 0x06, 0x82, 0x47, 0x7e, 0x3c, 0x9b, 0xf0, 0xf1, 0x51,
-        0x87, 0x46, 0x40, 0x0c,
-      ]),
-    },
   ];

   return vectors;
diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs
index b01ebce3e5a..8d58397783e 100644
--- a/test/fixtures/webcrypto/supports-modern-algorithms.mjs
+++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs
@@ -11,6 +11,8 @@ const pqc = hasOpenSSL(3, 5) || boringSSL;
 const argon2 = hasOpenSSL(3, 2) && !fips;
 const shake128 = crypto.getHashes().includes('shake128');
 const shake256 = crypto.getHashes().includes('shake256');
+const cshake128 = crypto.getHashes().includes('cshake128');
+const cshake256 = crypto.getHashes().includes('cshake256');
 const sha3 = crypto.getHashes().includes('sha3-256');
 const ocb = hasOpenSSL(3) && crypto.getCiphers().includes('aes-128-ocb');
 const kmac = hasOpenSSL(3) && crypto.getMacs().includes('kmac128');
@@ -22,19 +24,21 @@ export const vectors = {
     [false, 'cSHAKE128'],
     [shake128, { name: 'cSHAKE128', outputLength: 128 }],
     [shake128, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }],
-    [shake128 && kmac && !fips, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.from('KMAC') }],
+    [cshake128, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.from('KMAC') }],
     [false, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.from('SHAKE') }],
-    [shake128 && kmac && !fips, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(1) }],
+    [cshake128, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(1, 1) }],
+    [false, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(1) }],
     [false, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(513) }],
-    [shake128, { name: 'cSHAKE128', outputLength: 127 }],
+    [false, { name: 'cSHAKE128', outputLength: 127 }],
     [false, 'cSHAKE256'],
     [shake256, { name: 'cSHAKE256', outputLength: 256 }],
     [shake256, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }],
-    [shake256 && kmac && !fips, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.from('KMAC') }],
+    [cshake256, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.from('KMAC') }],
     [false, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.from('SHAKE') }],
-    [shake256 && kmac && !fips, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }],
+    [cshake256, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1, 1) }],
+    [false, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }],
     [false, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(513) }],
-    [shake256, { name: 'cSHAKE256', outputLength: 255 }],
+    [false, { name: 'cSHAKE256', outputLength: 255 }],
     [false, 'TurboSHAKE128'],
     [!fips, { name: 'TurboSHAKE128', outputLength: 128 }],
     [!fips, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x07 }],
@@ -86,9 +90,9 @@ export const vectors = {
     [false, 'KMAC128'],
     [false, 'KMAC256'],
     [kmac, { name: 'KMAC128', outputLength: 256 }],
-    [kmac && !fips, { name: 'KMAC128', outputLength: 255 }],
+    [false, { name: 'KMAC128', outputLength: 255 }],
     [kmac, { name: 'KMAC256', outputLength: 256 }],
-    [kmac && !fips, { name: 'KMAC256', outputLength: 255 }],
+    [false, { name: 'KMAC256', outputLength: 255 }],
   ],
   'generateKey': [
     [pqc, 'ML-DSA-44'],
@@ -109,10 +113,14 @@ export const vectors = {
     [kmac, 'KMAC256'],
     [kmac, { name: 'KMAC128', length: 256 }],
     [kmac, { name: 'KMAC256', length: 128 }],
-    [kmac && !fips, { name: 'KMAC128', length: 0 }],
-    [kmac && !fips, { name: 'KMAC256', length: 0 }],
-    [kmac && !fips, { name: 'KMAC128', length: 1 }],
-    [kmac && !fips, { name: 'KMAC256', length: 1 }],
+    [false, { name: 'KMAC128', length: 0 }],
+    [false, { name: 'KMAC256', length: 0 }],
+    [false, { name: 'KMAC128', length: 1 }],
+    [false, { name: 'KMAC128', length: 24 }],
+    [kmac, { name: 'KMAC128', length: 32 }],
+    [false, { name: 'KMAC256', length: 1 }],
+    [false, { name: 'KMAC256', length: 24 }],
+    [kmac, { name: 'KMAC256', length: 32 }],
   ],
   'importKey': [
     [pqc, 'ML-DSA-44'],
@@ -133,10 +141,14 @@ export const vectors = {
     [kmac, 'KMAC256'],
     [kmac, { name: 'KMAC128', length: 256 }],
     [kmac, { name: 'KMAC256', length: 128 }],
-    [kmac && !fips, { name: 'KMAC128', length: 0 }],
-    [kmac && !fips, { name: 'KMAC256', length: 0 }],
-    [kmac && !fips, { name: 'KMAC128', length: 1 }],
-    [kmac && !fips, { name: 'KMAC256', length: 1 }],
+    [false, { name: 'KMAC128', length: 0 }],
+    [false, { name: 'KMAC256', length: 0 }],
+    [false, { name: 'KMAC128', length: 1 }],
+    [false, { name: 'KMAC128', length: 24 }],
+    [kmac, { name: 'KMAC128', length: 32 }],
+    [false, { name: 'KMAC256', length: 1 }],
+    [false, { name: 'KMAC256', length: 24 }],
+    [kmac, { name: 'KMAC256', length: 32 }],
   ],
   'exportKey': [
     [pqc, 'ML-DSA-44'],
@@ -251,7 +263,7 @@ export const vectors = {
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 256 }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 255 }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 249 }],
-    [pqc && kmac && !fips, 'ML-KEM-768', { name: 'KMAC128', length: 255 }],
+    [false, 'ML-KEM-768', { name: 'KMAC128', length: 255 }],
     [hybridKems, 'MLKEM768-P256', 'HKDF'],
     [hybridKems, 'MLKEM768-X25519', 'HKDF'],
     [hybridKems, 'MLKEM1024-P384', 'HKDF'],
@@ -283,7 +295,7 @@ export const vectors = {
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 256 }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 255 }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 249 }],
-    [pqc && kmac && !fips, 'ML-KEM-768', { name: 'KMAC128', length: 255 }],
+    [false, 'ML-KEM-768', { name: 'KMAC128', length: 255 }],
     [hybridKems, 'MLKEM768-P256', 'HKDF'],
     [hybridKems, 'MLKEM768-X25519', 'HKDF'],
     [hybridKems, 'MLKEM1024-P384', 'HKDF'],
diff --git a/test/parallel/test-crypto-key-objects-to-crypto-key.js b/test/parallel/test-crypto-key-objects-to-crypto-key.js
index ba41eb64eb2..a5430df9e0f 100644
--- a/test/parallel/test-crypto-key-objects-to-crypto-key.js
+++ b/test/parallel/test-crypto-key-objects-to-crypto-key.js
@@ -14,7 +14,6 @@ const {
 } = require('crypto');
 const { hasFIPS } = require('../common/crypto');
 const { kSupportedAlgorithms } = require('internal/crypto/util');
-const fips = hasFIPS();
 const rejectsXCurves = hasFIPS(3, 5);

 const hashes = Object.keys(kSupportedAlgorithms.digest).filter((name) => {
@@ -139,19 +138,11 @@ function genericSecretVectors(name) {
   ];
 }

-function macInvalid(algorithm, invalidLengthMessage, allowZeroKey = false) {
+function macInvalid(algorithm, invalidLengthMessage, isKmac = false) {
   const key = createSecretKey(randomBytes(32));
   const usages = ['sign', 'verify'];

-  if (allowZeroKey && !fips) {
-    const zeroKey = createSecretKey(Buffer.alloc(0))
-      .toCryptoKey(algorithm, true, usages);
-    assert.strictEqual(zeroKey.algorithm.length, 0);
-
-    const explicitZeroKey = createSecretKey(Buffer.alloc(0))
-      .toCryptoKey({ ...algorithm, length: 0 }, true, usages);
-    assert.strictEqual(explicitZeroKey.algorithm.length, 0);
-  } else if (allowZeroKey) {
+  if (isKmac) {
     for (const zeroAlgorithm of [algorithm, { ...algorithm, length: 0 }]) {
       assert.throws(() => {
         createSecretKey(Buffer.alloc(0))
@@ -177,7 +168,7 @@ function macInvalid(algorithm, invalidLengthMessage, allowZeroKey = false) {

   assert.throws(
     () => key.toCryptoKey({ ...algorithm, length: 0 }, true, usages),
-    allowZeroKey && fips ? {
+    isKmac ? {
       name: 'NotSupportedError',
       message: 'Invalid key length',
     } : {
diff --git a/test/parallel/test-webcrypto-aes-kw-short-input.js b/test/parallel/test-webcrypto-aes-kw-short-input.js
index 09fe0268ab9..e11e19360de 100644
--- a/test/parallel/test-webcrypto-aes-kw-short-input.js
+++ b/test/parallel/test-webcrypto-aes-kw-short-input.js
@@ -6,18 +6,13 @@ if (!common.hasCrypto)
   common.skip('missing crypto');

 const assert = require('assert');
-const { getFips } = require('crypto');
-const { hasOpenSSL } = require('../common/crypto');
 const { subtle } = globalThis.crypto;

 (async () => {
   const keyToWrap = await subtle.importKey(
     'raw', new Uint8Array(16), 'AES-GCM', true, ['encrypt']);
-  let emptyKey;
-  if (hasOpenSSL(3) && getFips() !== 1) {
-    emptyKey = await subtle.importKey(
-      'raw-secret', new Uint8Array(0), 'KMAC128', true, ['sign']);
-  }
+  const shortKey = await subtle.importKey(
+    'raw', new Uint8Array(8), { name: 'HMAC', hash: 'SHA-256' }, true, ['sign']);

   for (const length of [128, 192, 256]) {
     const wrappingKey = await subtle.generateKey(
@@ -31,11 +26,8 @@ const { subtle } = globalThis.crypto;
         'HKDF', false, ['deriveBits']), { name: 'OperationError' });
     }

-    if (emptyKey !== undefined) {
-      await assert.rejects(subtle.wrapKey(
-        'raw-secret', emptyKey, wrappingKey, 'AES-KW'),
-                           { name: 'OperationError' });
-    }
+    await assert.rejects(subtle.wrapKey(
+      'raw', shortKey, wrappingKey, 'AES-KW'), { name: 'OperationError' });

     const wrapped = await subtle.wrapKey(
       'raw', keyToWrap, wrappingKey, 'AES-KW');
diff --git a/test/parallel/test-webcrypto-derivekey.js b/test/parallel/test-webcrypto-derivekey.js
index e1b663fa30f..318ca8d2500 100644
--- a/test/parallel/test-webcrypto-derivekey.js
+++ b/test/parallel/test-webcrypto-derivekey.js
@@ -284,7 +284,7 @@ const fips4 = hasFIPS(4);
   })().then(common.mustCall());
 }

-if (hasOpenSSL(3) && !hasFIPS()) {
+if (hasOpenSSL(3)) {
   (async () => {
     const derivedKeyAlgorithm = { name: 'KMAC128', length: 0 };
     const usages = ['sign'];
@@ -314,19 +314,9 @@ if (hasOpenSSL(3) && !hasFIPS()) {
         baseKeyAlgorithm,
         false,
         ['deriveKey']);
-      const derived = await subtle.deriveKey(
-        algorithm,
-        baseKey,
-        derivedKeyAlgorithm,
-        false,
-        usages);
-      assert.strictEqual(derived.algorithm.length, 0);
-
-      const signature = subtle.sign({
-        name: 'KMAC128',
-        outputLength: 256,
-      }, derived, new Uint8Array());
-      assert.strictEqual((await signature).byteLength, 32);
+      await assert.rejects(
+        subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, false, usages),
+        { name: 'NotSupportedError', message: 'Invalid key length' });
     }
   })().then(common.mustCall());
 }
diff --git a/test/parallel/test-webcrypto-digest.js b/test/parallel/test-webcrypto-digest.js
index 47a56a912d6..89bd86bd928 100644
--- a/test/parallel/test-webcrypto-digest.js
+++ b/test/parallel/test-webcrypto-digest.js
@@ -9,8 +9,7 @@ const assert = require('assert');
 const { Buffer } = require('buffer');
 const { subtle } = globalThis.crypto;
 const { createHash, getHashes } = require('crypto');
-const { hasOpenSSL, hasFIPS, isBoringSSL } = require('../common/crypto');
-const fips = hasFIPS();
+const { isBoringSSL } = require('../common/crypto');

 const kTests = [
   ['SHA-1', ['sha1'], 160],
@@ -265,16 +264,16 @@ if (getHashes().includes('shake128')) {
       new Uint8Array(0),
     );

-    const digest = await subtle.digest({ name: 'cSHAKE128', outputLength: 7 }, Buffer.alloc(1));
-    assert.strictEqual(digest.byteLength, 1);
-    assert.strictEqual(new Uint8Array(digest)[0] & 0b00000001, 0);
+    await assert.rejects(
+      subtle.digest({ name: 'cSHAKE128', outputLength: 7 }, Buffer.alloc(1)),
+      { name: 'NotSupportedError', message: 'Invalid CShakeParams outputLength' });

     await assert.rejects(
       subtle.digest(
         { name: 'cSHAKE128', outputLength: 0xffffffff },
         Buffer.alloc(1)),
       {
-        name: 'OperationError',
+        name: 'NotSupportedError',
         message: 'Invalid CShakeParams outputLength',
       });

@@ -291,22 +290,53 @@ if (getHashes().includes('shake128')) {
         message: 'Unsupported CShakeParams functionName',
       });

-    if (fips) return;
-
-    await assert.rejects(
-      subtle.digest(
-        {
-          name: 'cSHAKE128',
+    for (const name of ['cSHAKE128', 'cSHAKE256']) {
+      const supported = getHashes().includes(name.toLowerCase());
+      assert.deepStrictEqual(
+        await subtle.digest({
+          name,
           outputLength: 256,
-          customization: Buffer.alloc(513),
-        },
-        Buffer.alloc(1)),
-      {
-        name: 'OperationError',
-        message: 'CShakeParams.customization must be at most 512 bytes',
-      });
-
-    if (!hasOpenSSL(3)) return;
+          functionName: Buffer.alloc(0),
+          customization: Buffer.alloc(0),
+        }, Buffer.alloc(1)),
+        await subtle.digest({ name, outputLength: 256 }, Buffer.alloc(1)));
+
+      await assert.rejects(
+        subtle.digest({
+          name,
+          outputLength: 256,
+          customization: Buffer.alloc(513, 1),
+        }, Buffer.alloc(1)),
+        supported ? {
+          name: 'OperationError',
+          message: 'CShakeParams.customization must be at most 512 bytes',
+        } : {
+          name: 'NotSupportedError',
+          message: 'Unsupported CShakeParams customization',
+        });
+
+      await assert.rejects(
+        subtle.digest({
+          name,
+          outputLength: 256,
+          customization: Buffer.from([0x61, 0x00, 0x62]),
+        }, Buffer.alloc(1)),
+        { name: 'NotSupportedError', message: 'Unsupported CShakeParams customization' });
+
+      for (const params of [
+        { functionName: Buffer.from('KMAC') },
+        { customization: Buffer.from('Node.js') },
+      ]) {
+        const algorithm = { name, outputLength: 256, ...params };
+        if (supported) {
+          assert.strictEqual((await subtle.digest(algorithm, Buffer.alloc(1))).byteLength, 32);
+        } else {
+          await assert.rejects(subtle.digest(algorithm, Buffer.alloc(1)), {
+            name: 'NotSupportedError',
+          });
+        }
+      }
+    }

     const nistCShakeShortInput = Buffer.from('00010203', 'hex');
     const nistCShakeLongInput =
@@ -400,19 +430,23 @@ if (getHashes().includes('shake128')) {
                   'ca6f88db415829',
       },
     ]) {
-      assert.strictEqual(
-        Buffer.from(await subtle.digest(algorithm, data)).toString('hex'),
-        expected);
+      if (getHashes().includes(algorithm.name.toLowerCase())) {
+        assert.strictEqual(
+          Buffer.from(await subtle.digest(algorithm, data)).toString('hex'),
+          expected);
+      } else {
+        await assert.rejects(subtle.digest(algorithm, data), {
+          name: 'NotSupportedError',
+        });
+      }
     }

-    const truncated = Buffer.from(await subtle.digest(
-      { ...nistCShakeSample1.algorithm, outputLength: 255 },
-      nistCShakeSample1.data));
-    const expected = Buffer.from(nistCShakeSample1.expected, 'hex');
-    assert.strictEqual(truncated.byteLength, expected.byteLength);
-    assert.deepStrictEqual(
-      truncated.subarray(0, 31), expected.subarray(0, 31));
-    assert.strictEqual(truncated[31] & 0b00000001, 0);
-    assert.strictEqual(truncated[31] | 0b00000001, expected[31]);
+    if (getHashes().includes('cshake128')) {
+      await assert.rejects(
+        subtle.digest(
+          { ...nistCShakeSample1.algorithm, outputLength: 255 },
+          nistCShakeSample1.data),
+        { name: 'NotSupportedError', message: 'Invalid CShakeParams outputLength' });
+    }
   })().then(common.mustCall());
 }
diff --git a/test/parallel/test-webcrypto-export-import.js b/test/parallel/test-webcrypto-export-import.js
index 385ae8c65e9..63fdde05f1e 100644
--- a/test/parallel/test-webcrypto-export-import.js
+++ b/test/parallel/test-webcrypto-export-import.js
@@ -285,69 +285,35 @@ if (hasOpenSSL(3)) {
         [/* empty usages */]),
       { name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });

-    {
-      if (getFips() !== 1) {
-        const importedZeroImplicit = await subtle.importKey(
-          'raw-secret',
-          new Uint8Array(),
-          name,
-          true,
-          ['sign', 'verify']);
-        const importedZeroImplicitRaw =
-          await subtle.exportKey('raw-secret', importedZeroImplicit);
-        assert.strictEqual(importedZeroImplicit.algorithm.length, 0);
-        assert.strictEqual(importedZeroImplicitRaw.byteLength, 0);
+    for (const algorithm of [name, { name, length: 0 }]) {
+      await assert.rejects(
+        subtle.importKey(
+          'raw-secret', new Uint8Array(), algorithm, true, ['sign', 'verify']),
+        { name: 'NotSupportedError', message: 'Invalid key length' });
+    }

-        const importedZeroExplicit = await subtle.importKey(
-          'raw-secret',
-          new Uint8Array(),
-          { name, length: 0 },
-          true,
-          ['sign', 'verify']);
-        const importedZeroExplicitRaw =
-          await subtle.exportKey('raw-secret', importedZeroExplicit);
-        assert.strictEqual(importedZeroExplicit.algorithm.length, 0);
-        assert.strictEqual(importedZeroExplicitRaw.byteLength, 0);
-
-        await assert.rejects(
-          subtle.importKey(
-            'raw-secret',
-            new Uint8Array([0xff]),
-            { name, length: 0 },
-            true,
-            ['sign', 'verify']),
-          { name: 'DataError', message: 'Invalid key length' });
-
-        const generated = await subtle.generateKey(
-          { name, length: 9 },
-          true,
-          ['sign', 'verify']);
-        const generatedRaw = await subtle.exportKey('raw-secret', generated);
-        assert.strictEqual(generated.algorithm.length, 9);
-        assert.strictEqual(generatedRaw.byteLength, 2);
-        assert.strictEqual(new Uint8Array(generatedRaw)[1] & 0b01111111, 0);
+    await assert.rejects(
+      subtle.importKey(
+        'raw-secret',
+        new Uint8Array([0xff]),
+        { name, length: 0 },
+        true,
+        ['sign', 'verify']),
+      { name: 'NotSupportedError', message: 'Invalid key length' });
+
+    await assert.rejects(
+      subtle.generateKey({ name, length: 9 }, true, ['sign', 'verify']),
+      { name: 'NotSupportedError', message: 'Invalid key length' });

-        const importedExplicit = await subtle.importKey(
+    for (const byteLength of [1, 2]) {
+      await assert.rejects(
+        subtle.importKey(
           'raw-secret',
-          new Uint8Array([0xff, 0xff]),
+          new Uint8Array(byteLength).fill(0xff),
           { name, length: 9 },
           true,
-          ['sign', 'verify']);
-        const importedExplicitRaw = await subtle.exportKey('raw-secret', importedExplicit);
-        assert.strictEqual(importedExplicit.algorithm.length, 9);
-        assert.deepStrictEqual(
-          new Uint8Array(importedExplicitRaw),
-          new Uint8Array([0xff, 0x80]));
-
-        await assert.rejects(
-          subtle.importKey(
-            'raw-secret',
-            new Uint8Array([0xff]),
-            { name, length: 9 },
-            true,
-            ['sign', 'verify']),
-          { name: 'DataError', message: 'Invalid key length' });
-      }
+          ['sign', 'verify']),
+        { name: 'NotSupportedError', message: 'Invalid key length' });
     }
   }

diff --git a/test/parallel/test-webcrypto-fips-exceptions.mjs b/test/parallel/test-webcrypto-fips-exceptions.mjs
index ecc0f3c6989..b45740dc508 100644
--- a/test/parallel/test-webcrypto-fips-exceptions.mjs
+++ b/test/parallel/test-webcrypto-fips-exceptions.mjs
@@ -12,10 +12,10 @@ if (!hasFIPS(3))
   common.skip('requires OpenSSL >= 3 in FIPS mode');

 const require = createRequire(import.meta.url);
+const { getHashes } = require('node:crypto');
 const { internalBinding } = require('internal/test/binding');
 const { getCryptoKeyHandle } = require('internal/crypto/keys');
 const {
-  CShakeJob,
   KangarooTwelveJob,
   KmacJob,
   TurboShakeJob,
@@ -52,13 +52,6 @@ for (const createJob of [
     kCryptoJobWebCrypto, 'TurboSHAKE128', 0x1f, 16, data),
   () => new KangarooTwelveJob(
     kCryptoJobWebCrypto, 'KT128', undefined, 16, data),
-  () => new CShakeJob(
-    kCryptoJobWebCrypto,
-    'cSHAKE128',
-    data,
-    Buffer.from('KMAC'),
-    undefined,
-    128),
 ]) {
   assert.throws(createJob, {
     code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION',
@@ -66,48 +59,34 @@ for (const createJob of [
   });
 }

-const emptyCShake = {
-  name: 'cSHAKE128',
-  outputLength: 256,
-  customization: data,
-  functionName: data,
-};
-assert.strictEqual(SubtleCrypto.supports('digest', emptyCShake), true);
-
-for (const length of [1, 513]) {
-  const algorithm = {
-    name: 'cSHAKE128',
+for (const name of ['cSHAKE128', 'cSHAKE256']) {
+  const emptyCShake = {
+    name,
     outputLength: 256,
-    customization: new Uint8Array(length),
+    customization: data,
+    functionName: data,
   };
-  await assertFipsException(
-    'digest',
-    algorithm,
-    () => subtle.digest(algorithm, data),
-    'Unsupported CShakeParams customization');
+  assert.strictEqual(SubtleCrypto.supports('digest', emptyCShake), true);
+  assert.strictEqual((await subtle.digest(emptyCShake, data)).byteLength, 32);
+
+  for (const params of [
+    { customization: Buffer.from('Node.js') },
+    { functionName: Buffer.from('KMAC') },
+    { functionName: Buffer.from('KMAC'), customization: Buffer.from('Node.js') },
+  ]) {
+    const algorithm = { name, outputLength: 256, ...params };
+    const supported = getHashes().includes(name.toLowerCase());
+    assert.strictEqual(SubtleCrypto.supports('digest', algorithm), supported);
+    if (supported) {
+      assert.strictEqual((await subtle.digest(algorithm, data)).byteLength, 32);
+    } else {
+      await assert.rejects(subtle.digest(algorithm, data), {
+        name: 'NotSupportedError',
+      });
+    }
+  }
 }

-const functionName = {
-  name: 'cSHAKE256',
-  outputLength: 256,
-  functionName: Buffer.from('KMAC'),
-};
-await assertFipsException(
-  'digest',
-  functionName,
-  () => subtle.digest(functionName, data),
-  'Unsupported CShakeParams functionName');
-
-const bothCShakeParams = {
-  ...functionName,
-  customization: new Uint8Array(1),
-};
-await assertFipsException(
-  'digest',
-  bothCShakeParams,
-  () => subtle.digest(bothCShakeParams, data),
-  'Unsupported CShakeParams customization');
-
 for (const length of [0, 24, 33]) {
   const algorithm = { name: 'KMAC128', length };
   await assertFipsException(
@@ -170,7 +149,6 @@ await assert.rejects(
     getCryptoKeyHandle(key),
     'KMAC128',
     undefined,
-    32,
     9,
     data,
     undefined).run(),
diff --git a/test/parallel/test-webcrypto-fips-refresh.js b/test/parallel/test-webcrypto-fips-refresh.js
index 71459099356..4eff258b72b 100644
--- a/test/parallel/test-webcrypto-fips-refresh.js
+++ b/test/parallel/test-webcrypto-fips-refresh.js
@@ -6,7 +6,7 @@ if (!common.hasCrypto)
   common.skip('missing crypto');

 const assert = require('assert');
-const { getFips, setFips } = require('crypto');
+const { getFips, getHashes, setFips } = require('crypto');
 const { internalBinding } = require('internal/test/binding');
 const { getOptionValue } = require('internal/options');
 if (!internalBinding('crypto').testFipsCrypto())
@@ -25,8 +25,8 @@ try {
       name: 'KT128', outputLength: 128,
     }), !fips);
     assert.strictEqual(SubtleCrypto.supports('digest', {
-      name: 'cSHAKE128', outputLength: 128, customization: new Uint8Array(1),
-    }), !fips);
+      name: 'cSHAKE128', outputLength: 128, customization: new Uint8Array([1]),
+    }), getHashes().includes('cshake128'));
     assert.strictEqual(SubtleCrypto.supports('generateKey', {
       name: 'RSA-PSS', hash: 'SHA-256', modulusLength: 1024,
       publicExponent: new Uint8Array([1, 0, 1]),
diff --git a/test/parallel/test-webcrypto-keccak-byte-alignment.js b/test/parallel/test-webcrypto-keccak-byte-alignment.js
new file mode 100644
index 00000000000..ce3201210eb
--- /dev/null
+++ b/test/parallel/test-webcrypto-keccak-byte-alignment.js
@@ -0,0 +1,90 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const assert = require('assert');
+const { createSecretKey, subtle } = require('crypto');
+const { SubtleCrypto } = globalThis;
+const unsupported = { name: 'NotSupportedError' };
+
+(async () => {
+  const data = new Uint8Array([1, 2, 3]);
+  for (const name of ['cSHAKE128', 'cSHAKE256']) {
+    if (!SubtleCrypto.supports('digest', { name, outputLength: 128 }))
+      continue;
+
+    for (const customization of [undefined, new Uint8Array([1])]) {
+      if (!SubtleCrypto.supports('digest', { name, outputLength: 128, customization }))
+        continue;
+
+      for (let remainder = 1; remainder < 8; remainder++) {
+        const algorithm = { name, outputLength: 128 + remainder, customization };
+        assert.strictEqual(SubtleCrypto.supports('digest', algorithm), false);
+        await assert.rejects(subtle.digest(algorithm, data), unsupported);
+      }
+
+      const digest = await subtle.digest({ name, outputLength: 128, customization }, data);
+      assert.strictEqual(digest.byteLength, 16);
+    }
+
+    // Check the uint32 boundary without allocating a large digest.
+    assert.strictEqual(SubtleCrypto.supports('digest', { name, outputLength: 0xfffffff8 }), true);
+    for (const outputLength of [0xfffffff9, 0xffffffff]) {
+      assert.strictEqual(SubtleCrypto.supports('digest', { name, outputLength }), false);
+      await assert.rejects(subtle.digest({ name, outputLength }, data), unsupported);
+    }
+  }
+
+  const baseKey = await subtle.importKey('raw-secret', data, 'HKDF', false, ['deriveKey']);
+  const derivation = { name: 'HKDF', hash: 'SHA-256', salt: data, info: data };
+  const raw = Buffer.alloc(32, 1);
+  const secretKey = createSecretKey(raw);
+
+  for (const name of ['KMAC128', 'KMAC256']) {
+    if (!SubtleCrypto.supports('importKey', name))
+      continue;
+
+    const key = await subtle.importKey('raw-secret', raw, name, false, ['sign', 'verify']);
+    for (let remainder = 1; remainder < 8; remainder++) {
+      const algorithm = { name, length: 248 + remainder };
+      assert.strictEqual(SubtleCrypto.supports('generateKey', algorithm), false);
+      assert.strictEqual(SubtleCrypto.supports('importKey', algorithm), false);
+      assert.strictEqual(SubtleCrypto.supports('deriveKey', derivation, algorithm), false);
+      await assert.rejects(subtle.generateKey(algorithm, false, ['sign']), unsupported);
+      await assert.rejects(subtle.importKey('raw-secret', raw, algorithm, false, ['sign']), unsupported);
+      await assert.rejects(subtle.importKey(
+        'jwk', { kty: 'oct', k: raw.toString('base64url') }, algorithm, false, ['sign']), unsupported);
+      await assert.rejects(subtle.deriveKey(derivation, baseKey, algorithm, false, ['sign']), unsupported);
+      assert.throws(() => secretKey.toCryptoKey(algorithm, false, ['sign']), unsupported);
+
+      const params = { name, outputLength: 248 + remainder };
+      assert.strictEqual(SubtleCrypto.supports('sign', params), false);
+      assert.strictEqual(SubtleCrypto.supports('verify', params), false);
+      await assert.rejects(subtle.sign(params, key, data), unsupported);
+      await assert.rejects(subtle.verify(params, key, raw, data), unsupported);
+    }
+  }
+
+  if (SubtleCrypto.supports('generateKey', 'ML-KEM-768')) {
+    const { publicKey, privateKey } = await subtle.generateKey(
+      'ML-KEM-768', false, ['encapsulateBits', 'encapsulateKey', 'decapsulateKey']);
+    const { ciphertext } = await subtle.encapsulateBits('ML-KEM-768', publicKey);
+    for (const name of ['KMAC128', 'KMAC256']) {
+      if (!SubtleCrypto.supports('importKey', name))
+        continue;
+
+      const algorithm = { name, length: 255 };
+      assert.strictEqual(SubtleCrypto.supports('encapsulateKey', 'ML-KEM-768', algorithm), false);
+      assert.strictEqual(SubtleCrypto.supports('decapsulateKey', 'ML-KEM-768', algorithm), false);
+      await assert.rejects(subtle.encapsulateKey(
+        'ML-KEM-768', publicKey, algorithm, false, ['sign']), unsupported);
+      await assert.rejects(subtle.decapsulateKey(
+        'ML-KEM-768', privateKey, ciphertext, algorithm, false, ['sign']), unsupported);
+
+      assert.strictEqual(SubtleCrypto.supports('encapsulateKey', 'ML-KEM-768', { name, length: 256 }), true);
+      assert.strictEqual(SubtleCrypto.supports('decapsulateKey', 'ML-KEM-768', { name, length: 256 }), true);
+    }
+  }
+})().then(common.mustCall());
diff --git a/test/parallel/test-webcrypto-keygen-kmac.js b/test/parallel/test-webcrypto-keygen-kmac.js
index 33716095751..999e70df6a6 100644
--- a/test/parallel/test-webcrypto-keygen-kmac.js
+++ b/test/parallel/test-webcrypto-keygen-kmac.js
@@ -5,7 +5,7 @@ const common = require('../common');
 if (!common.hasCrypto)
   common.skip('missing crypto');

-const { hasFIPS, hasOpenSSL } = require('../common/crypto');
+const { hasOpenSSL } = require('../common/crypto');

 if (!hasOpenSSL(3))
   common.skip('requires OpenSSL >= 3');
@@ -13,7 +13,6 @@ if (!hasOpenSSL(3))
 const assert = require('assert');
 const { types: { isCryptoKey } } = require('util');
 const { subtle } = globalThis.crypto;
-const fips = hasFIPS();

 const usages = ['sign', 'verify'];

@@ -23,9 +22,6 @@ async function test(name, length) {
   if (length !== undefined)
     algorithm.length = length;

-  if (fips && length !== undefined &&
-      (length < 32 || length % 8 !== 0)) return;
-
   const generatedKey = await subtle.generateKey(algorithm, true, usages);

   assert(generatedKey);
@@ -45,12 +41,10 @@ async function test(name, length) {
 }

 const kTests = [
-  ['KMAC128', 0],
   ['KMAC128', 32],
   ['KMAC128', 128],
   ['KMAC128', 256],
   ['KMAC128'],
-  ['KMAC256', 0],
   ['KMAC256', 32],
   ['KMAC256', 128],
   ['KMAC256', 256],
@@ -60,3 +54,13 @@ const kTests = [
 const tests = Promise.all(kTests.map((args) => test(...args)));

 tests.then(common.mustCall());
+
+(async () => {
+  for (const name of ['KMAC128', 'KMAC256']) {
+    for (const length of [0, 8, 16, 24]) {
+      await assert.rejects(
+        subtle.generateKey({ name, length }, true, usages),
+        { name: 'NotSupportedError', message: 'Invalid key length' });
+    }
+  }
+})().then(common.mustCall());
diff --git a/test/parallel/test-webcrypto-prototype-pollution.mjs b/test/parallel/test-webcrypto-prototype-pollution.mjs
index 9f0b41ac10b..f485cb076f1 100644
--- a/test/parallel/test-webcrypto-prototype-pollution.mjs
+++ b/test/parallel/test-webcrypto-prototype-pollution.mjs
@@ -13,8 +13,7 @@ if (!common.hasCrypto) common.skip('missing crypto');

 const require = createRequire(import.meta.url);
 const { kSupportedAlgorithms } = require('internal/crypto/util');
-const { getFips } = require('node:crypto');
-const { hasOpenSSL } = require('../common/crypto');
+const { getFips, getHashes } = require('node:crypto');
 const { subtle } = globalThis.crypto;

 const TypedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
@@ -134,15 +133,15 @@ if (supports('digest', 'cSHAKE128')) {
         message: /Unsupported CShakeParams functionName/,
       })));

-  // asyncDigest() picks the cSHAKE job over plain SHAKE on a non-empty
+  // asyncDigest() picks cSHAKE over plain SHAKE on a non-empty
   // customization.
-  if (hasOpenSSL(3)) {
+  {
     const algorithm = {
       name: 'cSHAKE128',
       outputLength: 256,
       customization: new Uint8Array([1, 2, 3]),
     };
-    if (getFips() === 1) {
+    if (!getHashes().includes('cshake128')) {
       await withPoisoned(poisonTypedArrayByteLength(0), common.mustCall(() =>
         assert.rejects(subtle.digest(algorithm, data), {
           name: 'NotSupportedError',
diff --git a/test/parallel/test-webcrypto-sign-verify-kmac.js b/test/parallel/test-webcrypto-sign-verify-kmac.js
index ac0b738bcd5..5829ab3685c 100644
--- a/test/parallel/test-webcrypto-sign-verify-kmac.js
+++ b/test/parallel/test-webcrypto-sign-verify-kmac.js
@@ -5,38 +5,16 @@ const common = require('../common');
 if (!common.hasCrypto)
   common.skip('missing crypto');

-const { hasFIPS, hasOpenSSL } = require('../common/crypto');
+const { hasOpenSSL } = require('../common/crypto');

 if (!hasOpenSSL(3))
   common.skip('requires OpenSSL >= 3');

 const assert = require('assert');
 const { subtle } = globalThis.crypto;
-const fips = hasFIPS();
-const fips4 = hasFIPS(4);

 const vectors = require('../fixtures/crypto/kmac')();

-function isFipsProviderUnsupported(err) {
-  return err.name === 'OperationError' &&
-    err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED';
-}
-
-function usesNonFipsImplementation({ key, keyLength, outputLength }) {
-  const keyLengthInBits = keyLength ?? key.byteLength * 8;
-  return outputLength === 0 ||
-    outputLength % 8 !== 0 ||
-    keyLengthInBits < 32 ||
-    keyLengthInBits % 8 !== 0;
-}
-
-function isFips4Incompatible({ key, keyLength, outputLength }) {
-  const keyLengthInBits = keyLength ?? key.byteLength * 8;
-  return keyLengthInBits < 128 ||
-    keyLengthInBits % 8 !== 0 ||
-    outputLength % 8 !== 0;
-}
-
 async function testVerify({ algorithm,
                             key,
                             keyLength,
@@ -215,17 +193,8 @@ async function testSign({ algorithm,
   const variations = [];

   for (const vector of vectors) {
-    if (fips && usesNonFipsImplementation(vector)) continue;
-
-    if (fips4 && isFips4Incompatible(vector)) {
-      variations.push(assert.rejects(
-        testVerify(vector), isFipsProviderUnsupported));
-      variations.push(assert.rejects(
-        testSign(vector), isFipsProviderUnsupported));
-    } else {
-      variations.push(testVerify(vector));
-      variations.push(testSign(vector));
-    }
+    variations.push(testVerify(vector));
+    variations.push(testSign(vector));
   }

   await Promise.all(variations);
@@ -240,87 +209,54 @@ async function testSign({ algorithm,
     ['sign', 'verify']);
   const algorithm = {
     name: 'KMAC128',
-    outputLength: fips ? 16 : 9,
+    outputLength: 16,
     customization: new Uint8Array(),
   };
   const data = new Uint8Array([1, 2, 3]);

   const signature = await subtle.sign(algorithm, key, data);
   assert.strictEqual(signature.byteLength, 2);
-  if (!fips)
-    assert.strictEqual(new Uint8Array(signature)[1] & 0b01111111, 0);
   assert(await subtle.verify(algorithm, key, signature, data));

-  if (fips) {
-    const signature128 = await subtle.sign({
-      ...algorithm,
-      outputLength: 128,
-    }, key, data);
-    assert.strictEqual(signature128.byteLength, 16);
-    assert(await subtle.verify({
-      ...algorithm,
-      outputLength: 128,
-    }, key, signature128, data));
-  } else {
-    const signature16 = new Uint8Array(await subtle.sign({
-      ...algorithm,
-      outputLength: 16,
-    }, key, data));
-    signature16[1] &= 0b10000000;
-    assert.notDeepStrictEqual(new Uint8Array(signature), signature16);
-  }
+  const signature128 = await subtle.sign({
+    ...algorithm,
+    outputLength: 128,
+  }, key, data);
+  assert.strictEqual(signature128.byteLength, 16);
+  assert(await subtle.verify({
+    ...algorithm,
+    outputLength: 128,
+  }, key, signature128, data));

   const invalidSignature = new Uint8Array(signature);
-  if (fips)
-    invalidSignature[0] ^= 0b00000001;
-  else
-    invalidSignature[1] |= 0b00000001;
+  invalidSignature[0] ^= 0b00000001;
   assert(!(await subtle.verify(algorithm, key, invalidSignature, data)));

-  if (!fips) {
-    const nonByteKey = await subtle.importKey(
+  const nonByteOutput = { ...algorithm, outputLength: 9 };
+  await assert.rejects(
+    subtle.sign(nonByteOutput, key, data),
+    { name: 'NotSupportedError', message: 'Invalid KmacParams outputLength' });
+  await assert.rejects(
+    subtle.verify(nonByteOutput, key, signature, data),
+    { name: 'NotSupportedError', message: 'Invalid KmacParams outputLength' });
+
+  await assert.rejects(
+    subtle.importKey(
       'raw-secret',
       new Uint8Array([0xff, 0xff, 0xff, 0xff]),
       { name: 'KMAC128', length: 25 },
       false,
-      ['sign', 'verify']);
-    const nonByteKeySignature = subtle.sign({
-      ...algorithm,
-      outputLength: 16,
-    }, nonByteKey, data);
-    const result = await nonByteKeySignature;
-    assert.strictEqual(result.byteLength, 2);
-    assert(await subtle.verify({
-      ...algorithm,
-      outputLength: 16,
-    }, nonByteKey, result, data));
-  }
+      ['sign', 'verify']),
+    { name: 'NotSupportedError', message: 'Invalid key length' });
 })().then(common.mustCall());

 (async function() {
-  if (fips) return;
-
-  const data = new Uint8Array([1, 2, 3]);
-
   for (const name of ['KMAC128', 'KMAC256']) {
-    for (const keyData of [
-      new Uint8Array(),
-      new Uint8Array([1]),
-      new Uint8Array([1, 2, 3]),
-    ]) {
-      const key = await subtle.importKey(
-        'raw-secret',
-        keyData,
-        { name },
-        true,
-        ['sign', 'verify']);
-      assert.strictEqual(key.algorithm.length, keyData.byteLength * 8);
-
-      const algorithm = { name, outputLength: 256 };
-      const signature = subtle.sign(algorithm, key, data);
-      const result = await signature;
-      assert.strictEqual(result.byteLength, 32);
-      assert(await subtle.verify(algorithm, key, result, data));
+    for (const byteLength of [0, 1, 2, 3]) {
+      await assert.rejects(
+        subtle.importKey(
+          'raw-secret', new Uint8Array(byteLength), name, true, ['sign', 'verify']),
+        { name: 'NotSupportedError', message: 'Invalid key length' });
     }
   }
 })().then(common.mustCall());
diff --git a/test/parallel/test-webcrypto-wrap-unwrap.js b/test/parallel/test-webcrypto-wrap-unwrap.js
index b899e4f712d..1f8d45f02c2 100644
--- a/test/parallel/test-webcrypto-wrap-unwrap.js
+++ b/test/parallel/test-webcrypto-wrap-unwrap.js
@@ -485,20 +485,30 @@ async function testNonByteLengthWrapUnwrap({
   });

   if (hasOpenSSL(3) && getFips() !== 1) {
-    const kmacAlgorithm = { name: 'KMAC128' };
-    const kmacKey = await subtle.importKey(
-      'raw-secret',
-      new Uint8Array([0xff, 0xff]),
-      { ...kmacAlgorithm, length: 9 },
-      true,
-      ['sign', 'verify']);
-    await testNonByteLengthWrapUnwrap({
-      key: kmacKey,
-      formats: ['raw-secret', 'jwk'],
-      rawFormat: 'raw-secret',
-      explicitAlgorithm: { ...kmacAlgorithm, length: 9 },
-      implicitAlgorithm: kmacAlgorithm,
-    });
+    for (const name of ['KMAC128', 'KMAC256']) {
+      const keyData = new Uint8Array(32).fill(0xff);
+      const kmacKey = await subtle.importKey(
+        'raw-secret', keyData, name, true, ['sign', 'verify']);
+      const wrappingKey = await subtle.generateKey(
+        { name: 'AES-GCM', length: 128 }, true, ['wrapKey', 'unwrapKey']);
+
+      for (const [i, format] of ['raw-secret', 'jwk'].entries()) {
+        const wrapAlgorithm = { name: 'AES-GCM', iv: new Uint8Array(12).fill(i) };
+        const wrapped = await subtle.wrapKey(format, kmacKey, wrappingKey, wrapAlgorithm);
+        await assert.rejects(
+          subtle.unwrapKey(
+            format, wrapped, wrappingKey, wrapAlgorithm,
+            { name, length: 255 }, true, ['sign', 'verify']),
+          { name: 'NotSupportedError', message: 'Invalid key length' });
+
+        const unwrapped = await subtle.unwrapKey(
+          format, wrapped, wrappingKey, wrapAlgorithm,
+          { name, length: 256 }, true, ['sign', 'verify']);
+        assert.strictEqual(unwrapped.algorithm.length, 256);
+        assert.deepStrictEqual(
+          new Uint8Array(await subtle.exportKey('raw-secret', unwrapped)), keyData);
+      }
+    }
   }
 })().then(common.mustCall());

diff --git a/typings/internalBinding/crypto.d.ts b/typings/internalBinding/crypto.d.ts
index af6455f886f..e12016ec916 100644
--- a/typings/internalBinding/crypto.d.ts
+++ b/typings/internalBinding/crypto.d.ts
@@ -182,17 +182,6 @@ declare namespace InternalCryptoBinding {
     ): CryptoJobWebCrypto<ArrayBuffer>;
   }

-  interface CShakeJobConstructor {
-    new(
-      mode: CryptoJobWebCryptoMode,
-      algorithm: string,
-      data: ByteSource,
-      functionName: OptionalByteSource,
-      customization: OptionalByteSource,
-      outputLength: number,
-    ): CryptoJobWebCrypto<ArrayBuffer>;
-  }
-
   interface ChaCha20Poly1305CipherJobConstructor {
     new(
       mode: CryptoJobWebCryptoMode,
@@ -375,7 +364,6 @@ declare namespace InternalCryptoBinding {
       key: KeyObjectHandle,
       algorithm: string,
       customization: OptionalByteSource,
-      keyLength: number,
       outputLength: number,
       data: ByteSource,
       ...signature: MacJobSignatureArgs<S>
@@ -818,7 +806,6 @@ declare namespace InternalCryptoBinding {
 export interface CryptoBinding {
   AESCipherJob: InternalCryptoBinding.AESCipherJobConstructor;
   Argon2Job: InternalCryptoBinding.Argon2JobConstructor;
-  CShakeJob?: InternalCryptoBinding.CShakeJobConstructor;
   ChaCha20Poly1305CipherJob: InternalCryptoBinding.ChaCha20Poly1305CipherJobConstructor;
   CheckPrimeJob: InternalCryptoBinding.CheckPrimeJobConstructor;
   DHBitsJob: InternalCryptoBinding.DHBitsJobConstructor;