crypto: move DEP0198 to End-of-Life

Signed-off-by: Filip Skokan <panva.ip@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64000
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
Filip Skokan authored and GitHub committed 2026-06-21 11:18:48 +00:00
1 parent b734ff562f
commit b116861e88
10 files changed
+194 -106

No files matched your search

+12 -2
View File
@@ -3771,6 +3771,10 @@ and description of each available elliptic curve.
<!-- YAML
added: v0.1.92
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64000
description: The `outputLength` option is now required for XOF
hash functions without default output lengths.
- version: v12.8.0
pr-url: https://github.com/nodejs/node/pull/28805
description: The `outputLength` option was added for XOF hash functions.
@@ -3783,7 +3787,8 @@ changes:
Creates and returns a `Hash` object that can be used to generate hash digests
using the given `algorithm`. Optional `options` argument controls stream
behavior. For XOF hash functions such as `'shake256'`, the `outputLength` option
can be used to specify the desired output length in bytes.
specifies the desired output length in bytes. It is required for XOF hash
functions without a default output length.
The `algorithm` is dependent on the available algorithms supported by the
version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc.
@@ -4889,6 +4894,10 @@ added:
- v21.7.0
- v20.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64000
description: The `outputLength` option is now required for XOF
hash functions without default output lengths.
- version:
- v25.5.0
- v24.13.1
@@ -4909,7 +4918,8 @@ changes:
* `outputEncoding` {string} [Encoding][encoding] used to encode the
returned digest. **Default:** `'hex'`.
* `outputLength` {number} For XOF hash functions such as 'shake256',
the outputLength option can be used to specify the desired output length in bytes.
specifies the desired output length in bytes. This option is required for
XOF hash functions without a default output length.
* Returns: {string|Buffer}
A utility for creating one-shot hash digests of data. It can be faster than
+6 -2
View File
@@ -4354,6 +4354,9 @@ npx codemod@latest @nodejs/types-is-native-error
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64000
description: End-of-Life.
- version: v25.0.0
pr-url: https://github.com/nodejs/node/pull/59008
description: Runtime deprecation.
@@ -4365,9 +4368,10 @@ changes:
description: Documentation-only deprecation with support for `--pending-deprecation`.
-->
Type: Runtime
Type: End-of-Life
Creating SHAKE-128 and SHAKE-256 digests without an explicit `options.outputLength` is deprecated.
Creating SHAKE-128 and SHAKE-256 digests without an explicit
`options.outputLength` is no longer supported.
### DEP0199: `require('node:_http_*')`
-27
View File
@@ -3,7 +3,6 @@
const {
FunctionPrototypeCall,
ObjectSetPrototypeOf,
StringPrototypeReplace,
StringPrototypeToLowerCase,
Symbol,
TypedArrayPrototypeGetBuffer,
@@ -73,25 +72,6 @@ const LazyTransform = require('internal/streams/lazy_transform');
const kState = Symbol('kState');
const kFinalized = Symbol('kFinalized');
/**
* @param {string} name
* @returns {string}
*/
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) => {
const normalized = normalizeAlgorithm(algorithm);
return normalized === 'shake128' || normalized === 'shake256';
},
);
const emitHmacDigestDeprecation = getDeprecationWarningEmitter(
'DEP0206',
'Calling Hmac.digest() more than once is deprecated.',
@@ -117,9 +97,6 @@ function Hash(algorithm, options) {
this[kState] = {
[kFinalized]: false,
};
if (!isCopy && xofLen === undefined) {
maybeEmitDeprecationWarning(algorithm);
}
FunctionPrototypeCall(LazyTransform, this, options);
}
@@ -334,10 +311,6 @@ function hash(algorithm, input, options) {
outputLength += 0;
}
if (outputLength === undefined) {
maybeEmitDeprecationWarning(algorithm);
}
return oneShotDigest(algorithm, getCachedHashId(algorithm), getHashCache(),
input, normalized, encodingsMap[normalized], outputLength);
}
+48 -30
View File
@@ -246,6 +246,39 @@ const EVP_MD* GetDigestImplementation(Environment* env,
#endif
}
void MarkInvalidXofLength() {
EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
}
// DEP0198 EOL requires XOFs without an OpenSSL-defined default output length
// to fail when outputLength is omitted. OpenSSL 3.4 and later report a digest
// size of 0 for such XOFs, including SHAKE, which had weak historical defaults
// before OpenSSL 3.4. For older OpenSSL versions, identify those resolved
// EVP_MD values explicitly to keep the missing-outputLength error
// version-independent.
#if !OPENSSL_VERSION_PREREQ(3, 4)
bool IsShakeDigest(const EVP_MD* md) {
#if OPENSSL_VERSION_MAJOR >= 3
return EVP_MD_is_a(md, "SHAKE128") || EVP_MD_is_a(md, "SHAKE256");
#else
const char* name = OBJ_nid2sn(EVP_MD_type(md));
return name != nullptr &&
(strcmp(name, "SHAKE128") == 0 || strcmp(name, "SHAKE256") == 0);
#endif
}
#endif
bool ShouldRejectMissingXofLength(const EVP_MD* md, size_t default_length) {
if (default_length == 0) return true;
#if !OPENSSL_VERSION_PREREQ(3, 4)
return IsShakeDigest(md);
#else
static_cast<void>(md);
return false;
#endif
}
// crypto.digest(algorithm, algorithmId, algorithmCache,
// input, outputEncoding, outputEncodingId, outputLength)
void Hash::OneShotDigest(const FunctionCallbackInfo<Value>& args) {
@@ -287,18 +320,10 @@ void Hash::OneShotDigest(const FunctionCallbackInfo<Value>& args) {
} else if (is_xof) {
if (!args[6]->IsUndefined()) {
output_length = args[6].As<Uint32>()->Value();
} else if (output_length == 0) {
// This is to handle OpenSSL 3.4's breaking change in SHAKE128/256
// default lengths
// TODO(@panva): remove this behaviour when DEP0198 is End-Of-Life
const char* name = OBJ_nid2sn(EVP_MD_type(md));
if (name != nullptr) {
if (strcmp(name, "SHAKE128") == 0) {
output_length = 16;
} else if (strcmp(name, "SHAKE256") == 0) {
output_length = 32;
}
}
} else if (ShouldRejectMissingXofLength(md, output_length)) {
MarkInvalidXofLength();
return ThrowCryptoError(
env, ERR_get_error(), "Digest method not supported");
}
}
@@ -387,6 +412,12 @@ void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
void Hash::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
if (!args[1]->IsUndefined()) {
CHECK(args[1]->IsUint32());
xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
}
const Hash* orig = nullptr;
const EVP_MD* md = nullptr;
if (args[0]->IsObject()) {
@@ -397,12 +428,6 @@ void Hash::New(const FunctionCallbackInfo<Value>& args) {
md = GetDigestImplementation(env, args[0], args[2], args[3]);
}
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
if (!args[1]->IsUndefined()) {
CHECK(args[1]->IsUint32());
xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
}
Hash* hash = new Hash(env, args.This());
if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
return ThrowCryptoError(env, ERR_get_error(),
@@ -423,18 +448,11 @@ bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
md_len_ = mdctx_.getDigestSize();
// This is to handle OpenSSL 3.4's breaking change in SHAKE128/256
// default lengths
// 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 (mdctx_.hasXofFlag() && !xof_md_len.IsJust() &&
ShouldRejectMissingXofLength(md, md_len_)) {
MarkInvalidXofLength();
mdctx_.reset();
return false;
}
if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
@@ -4,17 +4,34 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
if (process.features.openssl_is_boringssl)
common.skip('not supported by BoringSSL');
const {
getHashes,
hash,
} = require('crypto');
const { hash } = require('crypto');
if (!getHashes().includes('shake128'))
common.skip('unsupported shake128 test');
common.expectWarning({
DeprecationWarning: {
DEP0198: 'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
}
});
const assert = require('assert');
{
hash('shake128', 'test');
const invalidXofLength = {
code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH',
name: 'Error',
message: /not XOF or invalid length/,
};
const shakeAlgorithms = [
'shake128',
'SHAKE128',
'shake256',
'SHAKE256',
];
for (const algorithm of shakeAlgorithms) {
assert.throws(() => hash(algorithm, 'test'), invalidXofLength);
assert.throws(() => hash(algorithm, 'test', 'hex'), invalidXofLength);
assert.throws(
() => hash(algorithm, 'test', { outputEncoding: 'buffer' }),
invalidXofLength);
assert.throws(() => hash(algorithm, 'test', {}), invalidXofLength);
}
@@ -4,19 +4,39 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const crypto = require('crypto');
if (!crypto.getHashes().includes('shake128')) {
const {
createHash,
getHashes,
} = require('crypto');
if (!getHashes().includes('shake128'))
common.skip('unsupported shake128 test');
const assert = require('assert');
const invalidXofLength = {
code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH',
name: 'Error',
message: /not XOF or invalid length/,
};
const shakeAlgorithms = [
'shake128',
'SHAKE128',
'shake256',
'SHAKE256',
];
for (const algorithm of shakeAlgorithms) {
assert.throws(() => createHash(algorithm), invalidXofLength);
assert.throws(() => createHash(algorithm, null), invalidXofLength);
assert.throws(() => createHash(algorithm, {}), invalidXofLength);
}
const { createHash } = require('crypto');
const shake128 = createHash('shake128', { outputLength: 5 });
const shake128Copy = shake128.copy({ outputLength: 5 });
common.expectWarning({
DeprecationWarning: {
DEP0198: 'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
}
});
{
createHash('shake128').update('test').digest();
}
assert.throws(() => shake128.copy(), invalidXofLength);
assert.throws(() => shake128.copy(null), invalidXofLength);
assert.throws(() => shake128.copy({}), invalidXofLength);
assert.throws(() => shake128Copy.copy(), invalidXofLength);
+20 -11
View File
@@ -8,10 +8,6 @@ common.expectWarning({
DeprecationWarning: [
['crypto.Hash constructor is deprecated.',
'DEP0179'],
...(process.features.openssl_is_boringssl ? [] : [[
'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
'DEP0198',
]]),
]
});
@@ -194,16 +190,29 @@ assert.throws(
// Test XOF hash functions and the outputLength option.
if (!process.features.openssl_is_boringssl) {
// Default outputLengths.
assert.strictEqual(crypto.createHash('shake128').digest('hex'),
const invalidXofLength = {
code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH',
name: 'Error',
message: /not XOF or invalid length/,
};
assert.throws(() => crypto.createHash('shake128'), invalidXofLength);
assert.throws(() => crypto.createHash('shake128', null), invalidXofLength);
assert.throws(() => crypto.createHash('shake256'), invalidXofLength);
assert.throws(() => crypto.createHash('shake256', {}), invalidXofLength);
assert.strictEqual(crypto.createHash('shake128', { outputLength: 16 })
.digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake128', null).digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake256').digest('hex'),
assert.strictEqual(crypto.createHash('shake256', { outputLength: 32 })
.digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');
assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 })
.copy() // Default outputLength.
const shake256 = crypto.createHash('shake256', { outputLength: 0 });
assert.throws(() => shake256.copy(), invalidXofLength);
assert.throws(() => shake256.copy(null), invalidXofLength);
assert.throws(() => shake256.copy({}), invalidXofLength);
assert.strictEqual(shake256.copy({ outputLength: 32 })
.digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');
+19 -7
View File
@@ -13,35 +13,47 @@ if (process.features.openssl_is_boringssl) {
// Test XOF hash functions and the outputLength option.
{
// Default outputLengths.
const invalidXofLength = {
code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH',
name: 'Error',
message: /not XOF or invalid length/,
};
assert.throws(() => crypto.hash('shake128', ''), invalidXofLength);
assert.throws(() => crypto.hash('shake128', '', 'hex'), invalidXofLength);
assert.throws(
() => crypto.hash('shake128', '', { outputEncoding: 'buffer' }),
invalidXofLength);
assert.throws(() => crypto.hash('shake256', ''), invalidXofLength);
assert.strictEqual(
crypto.hash('shake128', ''),
crypto.hash('shake128', '', { outputLength: 16 }),
'7f9c2ba4e88f827d616045507605853e'
);
assert.strictEqual(
crypto.hash('shake256', ''),
crypto.hash('shake256', '', { outputLength: 32 }),
'46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f'
);
// outputEncoding as an option.
assert.strictEqual(
crypto.hash('shake128', '', { outputEncoding: 'base64url' }),
crypto.hash('shake128', '', { outputEncoding: 'base64url', outputLength: 16 }),
'f5wrpOiPgn1hYEVQdgWFPg'
);
assert.strictEqual(
crypto.hash('shake256', '', { outputEncoding: 'base64url' }),
crypto.hash('shake256', '', { outputEncoding: 'base64url', outputLength: 32 }),
'RrndKwuojRMjOz_rdD7rJD_NUupiuBuCtQwnZG7Vdi8'
);
assert.deepStrictEqual(
crypto.hash('shake128', '', { outputEncoding: 'buffer' }),
crypto.hash('shake128', '', { outputEncoding: 'buffer', outputLength: 16 }),
Buffer.from('f5wrpOiPgn1hYEVQdgWFPg', 'base64url')
);
assert.deepStrictEqual(
crypto.hash('shake256', '', { outputEncoding: 'buffer' }),
crypto.hash('shake256', '', { outputEncoding: 'buffer', outputLength: 32 }),
Buffer.from('RrndKwuojRMjOz_rdD7rJD_NUupiuBuCtQwnZG7Vdi8', 'base64url')
);
+22 -3
View File
@@ -10,6 +10,17 @@ const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const fs = require('fs');
const invalidXofLengthCode = 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH';
function getXofOptions(method) {
try {
crypto.createHash(method);
} catch (err) {
if (err?.code !== invalidXofLengthCode) throw err;
return { outputLength: 16 };
}
}
// Test errors for invalid arguments.
[undefined, null, true, 1, () => {}, {}].forEach((invalid) => {
assert.throws(() => { crypto.hash(invalid, 'test'); }, { code: 'ERR_INVALID_ARG_TYPE' });
@@ -31,12 +42,20 @@ const methods = crypto.getHashes();
const input = fs.readFileSync(fixtures.path('utf8_test_text.txt'));
for (const method of methods) {
const xofOptions = getXofOptions(method);
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);
const options = xofOptions === undefined || outputEncoding === undefined ?
xofOptions : { ...xofOptions, outputEncoding };
const oldDigest = crypto.createHash(method, xofOptions)
.update(input)
.digest(outputEncoding || 'hex');
const digestFromBuffer = crypto.hash(
method, input, options === undefined ? outputEncoding : options);
assert.deepStrictEqual(digestFromBuffer, oldDigest,
`different result from ${method} with encoding ${outputEncoding}`);
const digestFromString = crypto.hash(method, input.toString(), outputEncoding);
const digestFromString = crypto.hash(
method, input.toString(), options === undefined ? outputEncoding : options);
assert.deepStrictEqual(digestFromString, oldDigest,
`different result from ${method} with encoding ${outputEncoding}`);
}
+8 -2
View File
@@ -151,8 +151,14 @@ if (!process.features.openssl_is_boringssl) {
}
validateList(crypto.getHashes());
// Make sure all of the hashes are supported by OpenSSL
for (const algo of crypto.getHashes())
crypto.createHash(algo);
for (const algo of crypto.getHashes()) {
try {
crypto.createHash(algo);
} catch (err) {
if (err?.code !== 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH') throw err;
crypto.createHash(algo, { outputLength: 0 });
}
}
// Assume that we have at least secp384r1.
assert.notStrictEqual(crypto.getCurves().length, 0);