Commit 718f044f078 for nodejs
commit 718f044f078fc3c79cd1701ff276f958a246bdb1
Author: Filip Skokan <panva.ip@gmail.com>
Date: Wed Sep 23 18:53:00 2026 +0200
crypto: validate digest output encodings
Reject unknown Hash and Hmac digest encodings before finalizing the
operation instead of silently returning a Buffer. Preserve buffer
output aliases and existing encoding coercion.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66247
Fixes: https://github.com/nodejs/node/issues/45189
Refs: https://github.com/nodejs/node/pull/45990
Reviewed-By: Xuguang Mei <meixuguang@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js
index 97bed5aab4a..d0d780ee407 100644
--- a/lib/internal/crypto/hash.js
+++ b/lib/internal/crypto/hash.js
@@ -52,6 +52,7 @@ const {
ERR_CRYPTO_HASH_UPDATE_FAILED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
+ ERR_UNKNOWN_ENCODING,
},
} = require('internal/errors');
@@ -176,13 +177,23 @@ Hash.prototype.update = function update(data, encoding) {
};
+function validateDigestEncoding(outputEncoding) {
+ // Explicit conversion of truthy values for backward compatibility.
+ outputEncoding &&= `${outputEncoding}`;
+ if (outputEncoding &&
+ normalizeEncoding(outputEncoding) === undefined &&
+ StringPrototypeToLowerCase(outputEncoding) !== 'buffer') {
+ throw new ERR_UNKNOWN_ENCODING(outputEncoding);
+ }
+ return outputEncoding;
+}
+
Hash.prototype.digest = function digest(outputEncoding) {
const state = this[kState];
if (state[kFinalized])
throw new ERR_CRYPTO_HASH_FINALIZED();
- // Explicit conversion of truthy values for backward compatibility.
- const ret = this[kHandle].digest(outputEncoding && `${outputEncoding}`);
+ const ret = this[kHandle].digest(validateDigestEncoding(outputEncoding));
state[kFinalized] = true;
return ret;
};
@@ -217,8 +228,7 @@ Hmac.prototype.digest = function digest(outputEncoding) {
return buf;
}
- // Explicit conversion of truthy values for backward compatibility.
- const ret = this[kHandle].digest(outputEncoding && `${outputEncoding}`);
+ const ret = this[kHandle].digest(validateDigestEncoding(outputEncoding));
state[kFinalized] = true;
return ret;
};
diff --git a/test/parallel/test-crypto-digest-output-encoding.js b/test/parallel/test-crypto-digest-output-encoding.js
new file mode 100644
index 00000000000..5f88f964843
--- /dev/null
+++ b/test/parallel/test-crypto-digest-output-encoding.js
@@ -0,0 +1,34 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const crypto = require('crypto');
+
+for (const create of [
+ () => crypto.createHash('sha256'),
+ () => crypto.createHmac('sha256', 'key'),
+]) {
+ const expected = create().update('test').digest();
+ for (const encoding of ['bad', { toString: () => 'bad' }]) {
+ const instance = create().update('test');
+ assert.throws(() => instance.digest(encoding), {
+ code: 'ERR_UNKNOWN_ENCODING',
+ message: 'Unknown encoding: bad',
+ });
+ // An invalid encoding must not finalize the operation.
+ assert.deepStrictEqual(instance.digest(), expected);
+ }
+ for (const encoding of [undefined, null, '', false, 0, 'buffer', 'BUFFER']) {
+ assert.deepStrictEqual(create().update('test').digest(encoding), expected);
+ }
+ for (const encoding of ['hex', 'HEX', 'base64', 'base64url', 'latin1', 'utf-8']) {
+ assert.strictEqual(create().update('test').digest(encoding),
+ expected.toString(encoding));
+ }
+ assert.strictEqual(
+ create().update('test').digest({ toString: () => 'hex' }),
+ expected.toString('hex'));
+}