Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 497b59b

Browse filesBrowse files
bnoordhuistargos
authored andcommitted
crypto: add Hash.prototype.copy() method
Make it possible to clone the internal state of a Hash object into a new Hash object, i.e., to fork the state of the object. Fixes: #29903 PR-URL: #29910 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: David Carlier <devnexen@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 83e225b commit 497b59b
Copy full SHA for 497b59b

File tree

Expand file treeCollapse file tree

5 files changed

+94
-8
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+94
-8
lines changed
Open diff view settings
Collapse file

‎doc/api/crypto.md‎

Copy file name to clipboardExpand all lines: doc/api/crypto.md
+37Lines changed: 37 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,43 @@ console.log(hash.digest('hex'));
10411041
// 6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50
10421042
```
10431043

1044+
### `hash.copy([options])`
1045+
<!-- YAML
1046+
added:
1047+
- version: REPLACEME
1048+
pr-url: https://github.com/nodejs/node/pull/29910
1049+
-->
1050+
1051+
* `options` {Object} [`stream.transform` options][]
1052+
* Returns: {Hash}
1053+
1054+
Creates a new `Hash` object that contains a deep copy of the internal state
1055+
of the current `Hash` object.
1056+
1057+
The optional `options` argument controls stream behavior. For XOF hash
1058+
functions such as `'shake256'`, the `outputLength` option can be used to
1059+
specify the desired output length in bytes.
1060+
1061+
An error is thrown when an attempt is made to copy the `Hash` object after
1062+
its [`hash.digest()`][] method has been called.
1063+
1064+
```js
1065+
// Calculate a rolling hash.
1066+
const crypto = require('crypto');
1067+
const hash = crypto.createHash('sha256');
1068+
1069+
hash.update('one');
1070+
console.log(hash.copy().digest('hex'));
1071+
1072+
hash.update('two');
1073+
console.log(hash.copy().digest('hex'));
1074+
1075+
hash.update('three');
1076+
console.log(hash.copy().digest('hex'));
1077+
1078+
// Etc.
1079+
```
1080+
10441081
### `hash.digest([encoding])`
10451082
<!-- YAML
10461083
added: v0.1.92
Collapse file

‎lib/internal/crypto/hash.js‎

Copy file name to clipboardExpand all lines: lib/internal/crypto/hash.js
+10-1Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ const kFinalized = Symbol('kFinalized');
3636
function Hash(algorithm, options) {
3737
if (!(this instanceof Hash))
3838
return new Hash(algorithm, options);
39-
validateString(algorithm, 'algorithm');
39+
if (!(algorithm instanceof _Hash))
40+
validateString(algorithm, 'algorithm');
4041
const xofLen = typeof options === 'object' && options !== null ?
4142
options.outputLength : undefined;
4243
if (xofLen !== undefined)
@@ -51,6 +52,14 @@ function Hash(algorithm, options) {
5152
Object.setPrototypeOf(Hash.prototype, LazyTransform.prototype);
5253
Object.setPrototypeOf(Hash, LazyTransform);
5354

55+
Hash.prototype.copy = function copy(options) {
56+
const state = this[kState];
57+
if (state[kFinalized])
58+
throw new ERR_CRYPTO_HASH_FINALIZED();
59+
60+
return new Hash(this[kHandle], options);
61+
};
62+
5463
Hash.prototype._transform = function _transform(chunk, encoding, callback) {
5564
this[kHandle].update(chunk, encoding);
5665
callback();
Collapse file

‎src/node_crypto.cc‎

Copy file name to clipboardExpand all lines: src/node_crypto.cc
+17-6Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4718,7 +4718,16 @@ void Hash::Initialize(Environment* env, Local<Object> target) {
47184718
void Hash::New(const FunctionCallbackInfo<Value>& args) {
47194719
Environment* env = Environment::GetCurrent(args);
47204720

4721-
const node::Utf8Value hash_type(env->isolate(), args[0]);
4721+
const Hash* orig = nullptr;
4722+
const EVP_MD* md = nullptr;
4723+
4724+
if (args[0]->IsObject()) {
4725+
ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
4726+
md = EVP_MD_CTX_md(orig->mdctx_.get());
4727+
} else {
4728+
const node::Utf8Value hash_type(env->isolate(), args[0]);
4729+
md = EVP_get_digestbyname(*hash_type);
4730+
}
47224731

47234732
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
47244733
if (!args[1]->IsUndefined()) {
@@ -4727,17 +4736,19 @@ void Hash::New(const FunctionCallbackInfo<Value>& args) {
47274736
}
47284737

47294738
Hash* hash = new Hash(env, args.This());
4730-
if (!hash->HashInit(*hash_type, xof_md_len)) {
4739+
if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
47314740
return ThrowCryptoError(env, ERR_get_error(),
47324741
"Digest method not supported");
47334742
}
4743+
4744+
if (orig != nullptr &&
4745+
0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
4746+
return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
4747+
}
47344748
}
47354749

47364750

4737-
bool Hash::HashInit(const char* hash_type, Maybe<unsigned int> xof_md_len) {
4738-
const EVP_MD* md = EVP_get_digestbyname(hash_type);
4739-
if (md == nullptr)
4740-
return false;
4751+
bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
47414752
mdctx_.reset(EVP_MD_CTX_new());
47424753
if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
47434754
mdctx_.reset();
Collapse file

‎src/node_crypto.h‎

Copy file name to clipboardExpand all lines: src/node_crypto.h
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ class Hash : public BaseObject {
591591
SET_MEMORY_INFO_NAME(Hash)
592592
SET_SELF_SIZE(Hash)
593593

594-
bool HashInit(const char* hash_type, v8::Maybe<unsigned int> xof_md_len);
594+
bool HashInit(const EVP_MD* md, v8::Maybe<unsigned int> xof_md_len);
595595
bool HashUpdate(const char* data, int len);
596596

597597
protected:
Collapse file

‎test/parallel/test-crypto-hash.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-crypto-hash.js
+29Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,27 @@ common.expectsError(
192192
assert.strictEqual(crypto.createHash('shake256').digest('hex'),
193193
'46b9dd2b0ba88d13233b3feb743eeb24' +
194194
'3fcd52ea62b81b82b50c27646ed5762f');
195+
assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 })
196+
.copy() // Default outputLength.
197+
.digest('hex'),
198+
'46b9dd2b0ba88d13233b3feb743eeb24' +
199+
'3fcd52ea62b81b82b50c27646ed5762f');
195200

196201
// Short outputLengths.
197202
assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 })
198203
.digest('hex'),
199204
'');
205+
assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 })
206+
.copy({ outputLength: 0 })
207+
.digest('hex'),
208+
'');
200209
assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 })
201210
.digest('hex'),
202211
'7f9c2ba4e8');
212+
assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 })
213+
.copy({ outputLength: 5 })
214+
.digest('hex'),
215+
'7f9c2ba4e8');
203216
assert.strictEqual(crypto.createHash('shake128', { outputLength: 15 })
204217
.digest('hex'),
205218
'7f9c2ba4e88f827d61604550760585');
@@ -249,3 +262,19 @@ common.expectsError(
249262
{ code: 'ERR_OUT_OF_RANGE' });
250263
}
251264
}
265+
266+
{
267+
const h = crypto.createHash('sha512');
268+
h.digest();
269+
common.expectsError(() => h.copy(), { code: 'ERR_CRYPTO_HASH_FINALIZED' });
270+
common.expectsError(() => h.digest(), { code: 'ERR_CRYPTO_HASH_FINALIZED' });
271+
}
272+
273+
{
274+
const a = crypto.createHash('sha512').update('abc');
275+
const b = a.copy();
276+
const c = b.copy().update('def');
277+
const d = crypto.createHash('sha512').update('abcdef');
278+
assert.strictEqual(a.digest('hex'), b.digest('hex'));
279+
assert.strictEqual(c.digest('hex'), d.digest('hex'));
280+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.