Skip to content

crypto: fix SHAKE128/256 breaking change introduced with OpenSSL 3.4 #58942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/api/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -4061,6 +4061,19 @@ Type: Documentation-only

The [`util.types.isNativeError`][] API is deprecated. Please use [`Error.isError`][] instead.

### DEP0198: Creating SHAKE-128 and SHAKE-256 digests without an explicit `options.outputLength`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58942
description: Documentation-only deprecation with support for `--pending-deprecation`.
-->

Type: Documentation-only (supports [`--pending-deprecation`][])

Creating SHAKE-128 and SHAKE-256 digests without an explicit `options.outputLength` is deprecated.

[DEP0142]: #dep0142-repl_builtinlibs
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
[RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3
Expand Down
31 changes: 31 additions & 0 deletions lib/internal/crypto/hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const {
ObjectSetPrototypeOf,
ReflectApply,
StringPrototypeReplace,
StringPrototypeToLowerCase,
Symbol,
} = primordials;
Expand Down Expand Up @@ -33,6 +34,8 @@ const {
lazyDOMException,
normalizeEncoding,
encodingsMap,
isPendingDeprecation,
getDeprecationWarningEmitter,
} = require('internal/util');

const {
Expand Down Expand Up @@ -63,6 +66,25 @@ const LazyTransform = require('internal/streams/lazy_transform');
const kState = Symbol('kState');
const kFinalized = Symbol('kFinalized');

/**
* @param {string} name
*/
function normalizeAlgorithm(name) {
return StringPrototypeReplace(StringPrototypeToLowerCase(name), '-', '');
}

const maybeEmitDeprecationWarning = getDeprecationWarningEmitter(
'DEP0198',
'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
undefined,
false,
(algorithm) => {
if (!isPendingDeprecation()) return false;
const normalized = normalizeAlgorithm(algorithm);
return normalized === 'shake128' || normalized === 'shake256';
},
);

function Hash(algorithm, options) {
if (!new.target)
return new Hash(algorithm, options);
Expand All @@ -80,6 +102,9 @@ function Hash(algorithm, options) {
this[kState] = {
[kFinalized]: false,
};
if (!isCopy && xofLen === undefined) {
maybeEmitDeprecationWarning(algorithm);
}
ReflectApply(LazyTransform, this, [options]);
}

Expand Down Expand Up @@ -213,6 +238,12 @@ function hash(algorithm, input, outputEncoding = 'hex') {
}
}
}
// TODO: ideally we have to ship https://github.com/nodejs/node/pull/58121 so
// that a proper DEP0198 deprecation can be done here as well.
Comment on lines +241 to +242
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// TODO: ideally we have to ship https://github.com/nodejs/node/pull/58121 so
// that a proper DEP0198 deprecation can be done here as well.
// TODO(panva): ideally we have to ship https://github.com/nodejs/node/pull/58121
// so that a proper DEP0198 deprecation can be done here as well.

const normalizedAlgorithm = normalizeAlgorithm(algorithm);
if (normalizedAlgorithm === 'shake128' || normalizedAlgorithm === 'shake256') {
return new Hash(algorithm).update(input).digest(normalized);
}
return oneShotDigest(algorithm, getCachedHashId(algorithm), getHashCache(),
input, normalized, encodingsMap[normalized]);
}
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ function getDeprecationWarningEmitter(
shouldEmitWarning = () => true,
) {
let warned = false;
return function() {
if (!warned && shouldEmitWarning()) {
return function(arg) {
if (!warned && shouldEmitWarning(arg)) {
warned = true;
if (code === 'ExperimentalWarning') {
process.emitWarning(msg, code, deprecated);
Expand Down Expand Up @@ -1011,4 +1011,6 @@ module.exports = {
setOwnProperty,
pendingDeprecate,
WeakReference,
isPendingDeprecation,
getDeprecationWarningEmitter,
};
12 changes: 12 additions & 0 deletions src/crypto/crypto_hash.cc
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,18 @@ bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
}

md_len_ = mdctx_.getDigestSize();
// TODO(@panva): remove this behaviour when DEP0198 is End-Of-Life
if (mdctx_.hasXofFlag() && !xof_md_len.IsJust() && md_len_ == 0) {
const char* name = OBJ_nid2sn(EVP_MD_type(md));
if (name != nullptr) {
if (strcmp(name, "SHAKE128") == 0) {
md_len_ = 16;
} else if (strcmp(name, "SHAKE256") == 0) {
md_len_ = 32;
}
}
}

if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
// This is a little hack to cause createHash to fail when an incorrect
// hashSize option was passed for a non-XOF hash function.
Expand Down
18 changes: 18 additions & 0 deletions test/parallel/test-crypto-default-shake-lengths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Flags: --pending-deprecation
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const { createHash } = require('crypto');

common.expectWarning({
DeprecationWarning: {
DEP0198: 'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
}
});

{
createHash('shake128').update('test').digest();
}
29 changes: 13 additions & 16 deletions test/parallel/test-crypto-hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const assert = require('assert');
const crypto = require('crypto');
const fs = require('fs');

const { hasOpenSSL } = require('../common/crypto');
const fixtures = require('../common/fixtures');

let cryptoType;
Expand Down Expand Up @@ -184,21 +183,19 @@ assert.throws(

// Test XOF hash functions and the outputLength option.
{
// Default outputLengths. Since OpenSSL 3.4 an outputLength is mandatory
if (!hasOpenSSL(3, 4)) {
assert.strictEqual(crypto.createHash('shake128').digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake128', null).digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake256').digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');
assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 })
.copy() // Default outputLength.
.digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');
}
// Default outputLengths.
assert.strictEqual(crypto.createHash('shake128').digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake128', null).digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake256').digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');
assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 })
.copy() // Default outputLength.
.digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');

// Short outputLengths.
assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 })
Expand Down
4 changes: 0 additions & 4 deletions test/parallel/test-crypto-oneshot-hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ if (!common.hasCrypto)
const assert = require('assert');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const { hasOpenSSL } = require('../common/crypto');
const fs = require('fs');

// Test errors for invalid arguments.
Expand All @@ -32,9 +31,6 @@ const methods = crypto.getHashes();
const input = fs.readFileSync(fixtures.path('utf8_test_text.txt'));

for (const method of methods) {
// Skip failing tests on OpenSSL 3.4.0
if (method.startsWith('shake') && hasOpenSSL(3, 4))
continue;
for (const outputEncoding of ['buffer', 'hex', 'base64', undefined]) {
const oldDigest = crypto.createHash(method).update(input).digest(outputEncoding || 'hex');
const digestFromBuffer = crypto.hash(method, input, outputEncoding);
Expand Down
Loading