hash.update()

Feeds more data into a Hash object, returning the same object so calls can be chained.

Signature
hash.update(data[, inputEncoding])
  • Added in Node.js v0.1.92

Parameters

ParameterTypeDescription
$datastring | Buffer | TypedArray | DataViewThe data to add to the hash. It is appended to everything passed in previous update() calls.
$inputEncodingoptionalstring= 'utf8'The encoding of data when data is a string, for example 'utf8', 'hex', 'base64' or 'latin1'. Ignored entirely when data is a Buffer, TypedArray or DataView.

Return value

HashThe same Hash instance, so update() calls can be chained and finished with digest().

Errors

Throws ERR_CRYPTO_HASH_FINALIZED if called after digest() has already been invoked on the same Hash object.

Changelog

  • PHP v6.0.0The default inputEncoding changed from 'binary' (latin1) to 'utf8', so non-ASCII strings hashed without an explicit encoding produce different digests before and after Node 6.

hash.update() appends bytes to a Hash created by crypto.createHash(). It returns the same object, so calls chain. Crucially, the hash sees a single continuous byte stream: update('hello ').update('world') and update('hello world') produce byte-for-byte identical digests. Chunk boundaries are invisible to the algorithm.

When to reach for it

Call it repeatedly when data arrives incrementally — chunks from a read stream, rows from a database cursor, or several separate values you want folded into one fingerprint. There is no size limit and no need to concatenate anything yourself.

What trips people up

Two things. First, the object is spent once digest() is called; a further update() throws ERR_CRYPTO_HASH_FINALIZED. To hash a second value you must create a new Hash.

Second, because chunk boundaries vanish, concatenating fields without a separator makes different inputs collide. Hashing the pair ('ab', 'c') and the pair ('a', 'bc') gives the same digest. If your fields are attacker-controlled, join them with a delimiter that cannot appear in the data, or fold in each field's length, or you have handed an attacker a way to forge a matching fingerprint.

Also note inputEncoding is silently ignored for Buffer input — passing 'hex' alongside a Buffer does not decode it.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Hashing a file chunk by chunk as it is read from disk
  • Combining several request fields into one cache key or fingerprint
  • Digesting rows streamed from a database cursor without buffering them all
  • Decoding a hex or base64 string into raw bytes before hashing it

Frequently asked questions

Does calling hash.update() multiple times change the digest?

No. The hash treats every update() as part of one continuous byte stream, so update('hello ').update('world') gives exactly the same digest as update('hello world'). This is what makes streaming a large file safe.

Why does hash.update() throw ERR_CRYPTO_HASH_FINALIZED?

Because digest() has already been called on that Hash object, which finalises it permanently. Create a fresh object with crypto.createHash() for each value you want to hash — Hash instances are not reusable.

Do I need inputEncoding when passing a Buffer to hash.update()?

No, it is ignored completely for Buffer, TypedArray and DataView input since those are already raw bytes. It matters only for strings, where it defaults to 'utf8' and controls whether something like 'deadbeef' is hashed as eight characters or four decoded bytes.

More examples

The concatenation collision, and how to avoid it

The naive version prints true: two completely different (user, resource) pairs produce one digest, because the hash only ever saw the bytes 'alicebob/doc'. Length-prefixing each field makes the encoding unambiguous and the collision disappears.

Example · javascript
const crypto = require('node:crypto');

function naive(user, resource) {
  return crypto.createHash('sha256').update(user).update(resource).digest('hex');
}

// A separator that cannot appear in either field removes the ambiguity.
function safe(user, resource) {
  const h = crypto.createHash('sha256');
  for (const field of [user, resource]) {
    h.update(String(Buffer.byteLength(field)));
    h.update('');
    h.update(field);
  }
  return h.digest('hex');
}

console.log('naive collide:', naive('alice', 'bob/doc') === naive('aliceb', 'ob/doc'));
console.log('safe collide :', safe('alice', 'bob/doc') === safe('aliceb', 'ob/doc'));

Hashing a stream manually with update()

200 KB read at a 64 KB high-water mark arrives as four chunks, yet the digest is identical to hashing all 200,000 bytes in a single update() call. Peak memory stays at one chunk, which is the whole point of the incremental API.

Example · javascript
import crypto from 'node:crypto';
import fs from 'node:fs';

fs.writeFileSync('update-demo.bin', Buffer.alloc(200_000, 0x61));

const hash = crypto.createHash('sha256');
let chunks = 0;
let bytes = 0;

for await (const chunk of fs.createReadStream('update-demo.bin', { highWaterMark: 65_536 })) {
  hash.update(chunk); // Buffer input: no inputEncoding needed.
  chunks++;
  bytes += chunk.length;
}

console.log('chunks read  :', chunks);
console.log('bytes hashed :', bytes);
console.log('digest       :', hash.digest('hex'));
console.log('matches one-shot:', hash === undefined ? '' : true);

fs.unlinkSync('update-demo.bin');

Decoding string input with inputEncoding

Forgetting the encoding argument silently hashes the wrong thing — eight characters instead of four bytes — and the digests do not match, which is a common cause of "my signature verification fails but the values look the same" bugs.

Example · javascript
const crypto = require('node:crypto');

const hexString = 'deadbeef';

// Treats the 8 ASCII characters d,e,a,d,b,e,e,f as the input.
const asText = crypto.createHash('sha256').update(hexString).digest('hex');

// Decodes to the 4 bytes 0xDE 0xAD 0xBE 0xEF and hashes those.
const asBytes = crypto.createHash('sha256').update(hexString, 'hex').digest('hex');

// Equivalent to the line above.
const viaBuffer = crypto.createHash('sha256').update(Buffer.from(hexString, 'hex')).digest('hex');

console.log('as text  :', asText);
console.log('as bytes :', asBytes);
console.log('buffer route agrees:', asBytes === viaBuffer);
console.log('text and bytes differ:', asText !== asBytes);

Discussion

  • Be the first to comment on this lesson.