The ProblemYou are likely using the built-in crypto module to generate hashes for passwords, files, or API keys. Your code works for the first item, but the moment you process a second one, the application crashes. The console displays a specific error: Error [ERR_CRYPTO_HASH_FINALIZED]: Digest already called.
This error usually pops up when processing a batch of data, such as an array of 1,000 strings or a directory of log files. It happens because you are trying to use a hash object that has already finished its job.
TL;DR: The Quick FixIn Node.js, a Hash object is a single-use state machine. Once you call .digest(), the object is effectively dead. You cannot add more data, and you cannot ask for the result a second time.
The Fix: Instantiate a new hash for every unique piece of data. Move your crypto.createHash() call inside your loop or function logic.
// โ WRONG: Reusing the same object for multiple items
const hash = crypto.createHash('sha256');
items.forEach(item => {
const result = hash.update(item).digest('hex'); // Crashes on the 2nd iteration
});
// โ
RIGHT: Creating a fresh instance for every iteration
items.forEach(item => {
const hash = crypto.createHash('sha256');
const result = hash.update(item).digest('hex');
});
Why This HappensThe crypto.Hash class is designed to handle data in chunks. You can call .update() multiple times to pipe in fragments of a large 50MB file. However, calling .digest() signals that the input is complete.
At this point, Node.js calculates the final bits, clears the internal buffers, and locks the state. This design prevents memory leaks and ensures cryptographic integrity. If you try to .update() or .digest() again, the engine throws ERR_CRYPTO_HASH_FINALIZED because there is no active session to work with.
Common Scenarios### 1. Hashing in a LoopDevelopers often try to optimize code by declaring the hasher outside a loop. While this works for simple variables, it fails for cryptographic objects.
// This code will fail after processing the first file
const hasher = crypto.createHash('md5');
const files = ['report_v1.pdf', 'report_v2.pdf'];
const hashes = files.map(f => {
const content = fs.readFileSync(f);
return hasher.update(content).digest('hex');
});
Solution: Always move the createHash call inside the map or forEach block.
2. Shared Utility FunctionsPassing a hash instance as a parameter can lead to unexpected crashes. If a helper function calls .digest(), that instance becomes useless to the rest of your program.
function finalizeHash(hasher, data) {
return hasher.update(data).digest('hex');
}
// If you pass the same 'hasher' to this twice, the second call fails.
Verification and DebuggingTo verify your fix, test your logic with an array of at least three items. If the script completes without a crash, your scoping is correct. You should also verify that the generated strings match expected industry standards.
I often use the Hash Generator on ToolCraft to double-check my Node.js output. It is a local browser tool that doesn't upload your data. Paste your raw string there, select sha256 or md5, and compare the hex output to your console. If they match, your implementation is secure and accurate.
Best Practices for Prevention- Scope Locally: Keep hash instances inside the smallest possible scope.- Use Helper Wrappers: Wrap hashing logic in a dedicated function that returns the final string. This ensures a new instance is born and dies within that function call.- Modern Streams: When hashing large files, use the stream/promises API. This manages the lifecycle of the hash object automatically.```
const { createHash } = require('node:crypto'); const { pipeline } = require('node:stream/promises'); const { createReadStream } = require('node:fs');
async function hashLargeFile(filePath) { const hash = createHash('sha256'); const source = createReadStream(filePath);
// pipeline handles the 'end' event and finalizes the hash safely
await pipeline(source, hash);
return hash.digest('hex');
}
Using the pattern above prevents `ERR_CRYPTO_HASH_FINALIZED` by tying the hash lifecycle directly to the file's read stream. Once the stream ends, the hash is ready for a single, final `digest()` call.

