GeezerVison is showing github.com — crypto: add Hash.prototype.copy() method · nodejs/node@497b59b
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -1041,6 +1041,43 @@ console.log(hash.digest('hex')); | 1041 | 1041 | // 6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50 | 1042 | 1042 | ``` | 1043 | 1043 |
| | 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 | + | 1044 | 1081 | ### `hash.digest([encoding])` | 1045 | 1082 | <!-- YAML | 1046 | 1083 | added: v0.1.92 | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/hash.js +10-1Lines changed: 10 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -36,7 +36,8 @@ const kFinalized = Symbol('kFinalized'); | 36 | 36 | function Hash(algorithm, options) { | 37 | 37 | if (!(this instanceof Hash)) | 38 | 38 | return new Hash(algorithm, options); | 39 | | - validateString(algorithm, 'algorithm'); | | 39 | + if (!(algorithm instanceof _Hash)) | | 40 | + validateString(algorithm, 'algorithm'); | 40 | 41 | const xofLen = typeof options === 'object' && options !== null ? | 41 | 42 | options.outputLength : undefined; | 42 | 43 | if (xofLen !== undefined) | @@ -51,6 +52,14 @@ function Hash(algorithm, options) { | 51 | 52 | Object.setPrototypeOf(Hash.prototype, LazyTransform.prototype); | 52 | 53 | Object.setPrototypeOf(Hash, LazyTransform); | 53 | 54 |
| | 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 | + | 54 | 63 | Hash.prototype._transform = function _transform(chunk, encoding, callback) { | 55 | 64 | this[kHandle].update(chunk, encoding); | 56 | 65 | callback(); | |
Collapse file Copy file name to clipboardExpand all lines: src/node_crypto.cc +17-6Lines changed: 17 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -4718,7 +4718,16 @@ void Hash::Initialize(Environment* env, Local<Object> target) { | 4718 | 4718 | void Hash::New(const FunctionCallbackInfo<Value>& args) { | 4719 | 4719 | Environment* env = Environment::GetCurrent(args); | 4720 | 4720 |
| 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 | + } | 4722 | 4731 |
| 4723 | 4732 | Maybe<unsigned int> xof_md_len = Nothing<unsigned int>(); | 4724 | 4733 | if (!args[1]->IsUndefined()) { | @@ -4727,17 +4736,19 @@ void Hash::New(const FunctionCallbackInfo<Value>& args) { | 4727 | 4736 | } | 4728 | 4737 |
| 4729 | 4738 | 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)) { | 4731 | 4740 | return ThrowCryptoError(env, ERR_get_error(), | 4732 | 4741 | "Digest method not supported"); | 4733 | 4742 | } | | 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 | + } | 4734 | 4748 | } | 4735 | 4749 |
| 4736 | 4750 |
| 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) { | 4741 | 4752 | mdctx_.reset(EVP_MD_CTX_new()); | 4742 | 4753 | if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) { | 4743 | 4754 | mdctx_.reset(); | |
Collapse file Copy file name to clipboardExpand all lines: src/node_crypto.h +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -591,7 +591,7 @@ class Hash : public BaseObject { | 591 | 591 | SET_MEMORY_INFO_NAME(Hash) | 592 | 592 | SET_SELF_SIZE(Hash) | 593 | 593 |
| 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); | 595 | 595 | bool HashUpdate(const char* data, int len); | 596 | 596 |
| 597 | 597 | protected: | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-crypto-hash.js +29Lines changed: 29 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -192,14 +192,27 @@ common.expectsError( | 192 | 192 | assert.strictEqual(crypto.createHash('shake256').digest('hex'), | 193 | 193 | '46b9dd2b0ba88d13233b3feb743eeb24' + | 194 | 194 | '3fcd52ea62b81b82b50c27646ed5762f'); | | 195 | + assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 }) | | 196 | + .copy() // Default outputLength. | | 197 | + .digest('hex'), | | 198 | + '46b9dd2b0ba88d13233b3feb743eeb24' + | | 199 | + '3fcd52ea62b81b82b50c27646ed5762f'); | 195 | 200 |
| 196 | 201 | // Short outputLengths. | 197 | 202 | assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 }) | 198 | 203 | .digest('hex'), | 199 | 204 | ''); | | 205 | + assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 }) | | 206 | + .copy({ outputLength: 0 }) | | 207 | + .digest('hex'), | | 208 | + ''); | 200 | 209 | assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 }) | 201 | 210 | .digest('hex'), | 202 | 211 | '7f9c2ba4e8'); | | 212 | + assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 }) | | 213 | + .copy({ outputLength: 5 }) | | 214 | + .digest('hex'), | | 215 | + '7f9c2ba4e8'); | 203 | 216 | assert.strictEqual(crypto.createHash('shake128', { outputLength: 15 }) | 204 | 217 | .digest('hex'), | 205 | 218 | '7f9c2ba4e88f827d61604550760585'); | @@ -249,3 +262,19 @@ common.expectsError( | 249 | 262 | { code: 'ERR_OUT_OF_RANGE' }); | 250 | 263 | } | 251 | 264 | } | | 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 | +} |
|