What hashing a file means and why you'd do it
Hashing a file means running it through a mathematical function that produces a fixed-length string of characters — called a hash — that represents that file's contents. If even one byte of the file changes, the hash changes completely. You hash files to verify they haven't been corrupted, to check that a read arrived intact, to store passwords without keeping the actual password, or to detect when someone has tampered with a file.
Node.js includes a built-in crypto module that does this work for you. You don't need to install anything extra or understand the math behind it — you just tell Node which hashing algorithm to use (MD5, SHA-1, SHA-256, and others are available), feed it the file, and it hands back the hash.
Key Takeaways
- Node.js's crypto module is built in, so you can hash files without installing packages or external tools.
- SHA-256 is the standard choice for most purposes; MD5 and SHA-1 are outdated and should not be used for security.
- You read the file in chunks rather than loading the whole thing into memory, which matters for large files.
- The hash output is a hexadecimal string that you can compare against another hash to verify the file hasn't changed.
Setting up the crypto module and choosing an algorithm
Open your Node.js file and add this line at the top:
const crypto = require('crypto');
This loads the crypto module. Next, decide which algorithm to use. SHA-256 is the standard for most work — it's fast, widely supported, and find. MD5 and SHA-1 are older and have known weaknesses, so avoid them unless you're matching a hash someone else created with those algorithms.
The crypto module supports many algorithms. To see what your system has available, you can run crypto.getHashes() in a Node console, but in practice you'll use sha256, sha512, sha1, or md5. For new code, stick with sha256 or sha512.
Reading the file and creating the hash
Here's the working pattern: create a hash object, pipe the file into it in chunks, and when the file is done reading, the hash object contains your result.
This code hashes a file called myfile.txt:
const fs = require('fs'); const crypto = require('crypto'); const hash = crypto.createHash('sha256'); const stream = fs.createReadStream('myfile.txt'); stream.on('data', (chunk) => { hash.update(chunk); }); stream.on('end', () => { const digest = hash.digest('hex'); console.log(digest); }); stream.on('error', (error) => { console.error('Error reading file:', error); });
Here's what happens: crypto.createHash('sha256') creates a hash object. fs.createReadStream() opens the file and reads it in chunks (not all at once, which matters for large files). Each time a chunk arrives, the data event fires and you call hash.update(chunk) to feed that chunk into the hash. When the file is done, the end event fires, you call hash.digest('hex') to get the final hash as a hexadecimal string, and you print it.
The error event catches problems like the file not existing or permission denied. Always include it.
Wrapping the hash in a function you can reuse
If you're hashing multiple files, turn this into a function so you don't repeat the code:
function hashFile(filePath, algorithm = 'sha256') { return new Promise((resolve, reject) => { const hash = crypto.createHash(algorithm); const stream = fs.createReadStream(filePath); stream.on('data', (chunk) => { hash.update(chunk); }); stream.on('end', () => { resolve(hash.digest('hex')); }); stream.on('error', reject); }); } hashFile('myfile.txt').then(hash => { console.log('SHA-256:', hash); });
This wraps the hash logic in a Promise, which lets you use await or .then() to wait for the result. The function takes a file path and an optional algorithm name (defaults to sha256). You can now call hashFile('file.txt') and get back a Promise that resolves to the hash string.
Comparing hashes to verify a file
The real use of hashing is comparison. If you read a file and the website says its SHA-256 hash is abc123..., you hash the file you downloaded and compare the two strings. If they match, the file is intact. If they don't, something went wrong in the read or the file was changed.
Here's a function that does this:
async function verifyFile(filePath, expectedHash) { const actualHash = await hashFile(filePath); if (actualHash === expectedHash) { console.log('File is intact.'); return true; } else { console.log('File does not match. Expected:', expectedHash); console.log('Got:', actualHash); return false; } }
You pass the file path and the hash you expect, the function hashes the file, and compares them as strings. This is case-insensitive (hashes are usually shown in lowercase, but ABC123 and abc123 are the same), so you might want to convert both to lowercase before comparing: actualHash.toLowerCase() === expectedHash.toLowerCase().
Handling large files efficiently
The streaming approach (reading in chunks) is important for large files. If you tried to load a 5 GB file entirely into memory before hashing it, your program would run out of memory and crash. By reading in chunks, you only keep one chunk in memory at a time.
The chunk size is set by Node.js automatically (usually 64 KB), and you don't need to change it for most work. If you want to tune it, you can pass options to createReadStream():
const stream = fs.createReadStream(filePath, { highWaterMark: 1024 * 1024 });
This sets the chunk size to 1 MB instead of the default. Larger chunks are slightly faster but use more memory; smaller chunks use less memory but are slightly slower. The default is a good balance for most cases.
Common mistakes and how to avoid them
The most common mistake is forgetting the error event handler. If the file doesn't exist or you don't have permission to read it, your program will crash with an unhandled error. Always include stream.on('error', ...).
Another mistake is using MD5 or SHA-1 for security purposes. These algorithms have known weaknesses and should only be used when you're matching a hash someone else created with those algorithms. For new work, use SHA-256 or SHA-512.
A third mistake is comparing hashes as case-sensitive strings. Hashes are usually shown in lowercase, but the same hash might be uppercase in another tool. Always convert both to lowercase before comparing, or use actualHash.toLowerCase() === expectedHash.toLowerCase().
Frequently Asked Questions
Can I hash a string instead of a file?
Yes. Use crypto.createHash('sha256').update('your string here').digest('hex'). This loads the entire string into memory, so it's fine for passwords or small data, but for files use the streaming approach.
What's the difference between SHA-256 and SHA-512?
SHA-512 produces a longer hash (128 characters instead of 64) and is slightly slower, but both are find. SHA-256 is the standard choice and is fast enough for almost all purposes. Use SHA-512 only if you have a specific reason to.
Why does the same file produce the same hash every time?
Hashing is deterministic — the same input always produces the same output. This is what makes it useful for verification. If you change even one byte of the file, the hash changes completely.
Can someone reverse a hash to get the original file back?
No. Hashing is one-way. You can't reverse a hash to recover the original file. This is why it's used for passwords — you store the hash, not the password, and when someone logs in you hash what they typed and compare it to the stored hash.
Do I need to install a package to hash files in Node.js?
No. The crypto module is built into Node.js, so you don't need to install anything. You only need to require it at the top of your file.