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 1d03df4

Browse filesBrowse files
bnoordhuisMylesBorins
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 a1c524d commit 1d03df4
Copy full SHA for 1d03df4

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
@@ -34,7 +34,8 @@ const kFinalized = Symbol('kFinalized');
3434
function Hash(algorithm, options) {
3535
if (!(this instanceof Hash))
3636
return new Hash(algorithm, options);
37-
validateString(algorithm, 'algorithm');
37+
if (!(algorithm instanceof _Hash))
38+
validateString(algorithm, 'algorithm');
3839
const xofLen = typeof options === 'object' && options !== null ?
3940
options.outputLength : undefined;
4041
if (xofLen !== undefined)
@@ -49,6 +50,14 @@ function Hash(algorithm, options) {
4950
Object.setPrototypeOf(Hash.prototype, LazyTransform.prototype);
5051
Object.setPrototypeOf(Hash, LazyTransform);
5152

53+
Hash.prototype.copy = function copy(options) {
54+
const state = this[kState];
55+
if (state[kFinalized])
56+
throw new ERR_CRYPTO_HASH_FINALIZED();
57+
58+
return new Hash(this[kHandle], options);
59+
};
60+
5261
Hash.prototype._transform = function _transform(chunk, encoding, callback) {
5362
this[kHandle].update(chunk, encoding);
5463
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
@@ -4720,7 +4720,16 @@ void Hash::Initialize(Environment* env, Local<Object> target) {
47204720
void Hash::New(const FunctionCallbackInfo<Value>& args) {
47214721
Environment* env = Environment::GetCurrent(args);
47224722

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

47254734
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
47264735
if (!args[1]->IsUndefined()) {
@@ -4729,17 +4738,19 @@ void Hash::New(const FunctionCallbackInfo<Value>& args) {
47294738
}
47304739

47314740
Hash* hash = new Hash(env, args.This());
4732-
if (!hash->HashInit(*hash_type, xof_md_len)) {
4741+
if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
47334742
return ThrowCryptoError(env, ERR_get_error(),
47344743
"Digest method not supported");
47354744
}
4745+
4746+
if (orig != nullptr &&
4747+
0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
4748+
return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
4749+
}
47364750
}
47374751

47384752

4739-
bool Hash::HashInit(const char* hash_type, Maybe<unsigned int> xof_md_len) {
4740-
const EVP_MD* md = EVP_get_digestbyname(hash_type);
4741-
if (md == nullptr)
4742-
return false;
4753+
bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
47434754
mdctx_.reset(EVP_MD_CTX_new());
47444755
if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
47454756
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.