Node.js digest to string conversion: crypto.createHash('sha256').update(input).digest('hex') returns the hex string, 'base64' returns the base64, no argument returns a Buffer. The right pick: hex for debugging and HTTP headers, base64 for compactness, Buffer for performance. The team that uses the right encoding for the right context writes idiomatic crypto code.
Table of contents
- The basic pattern
- The three output encodings
- Common use cases
- Streaming large data
- Common gotchas
- How this fits the rest of the stack
- FAQ
The basic pattern
const crypto = require('crypto');
const hash = crypto.createHash('sha256');
hash.update('hello world');
const hex = hash.digest('hex');
// hex = 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'
The flow: create a hash object, feed it data (one or more update calls), call digest to get the result. The digest call finalizes the hash - you cannot call update after.
The three output encodings
hex - the human-readable string. 64 characters for SHA-256, 40 for SHA-1, 128 for SHA-512. Used in HTTP headers (ETag, Content-MD5), git commit hashes, and debugging.
const hex = crypto.createHash('sha256').update('hello').digest('hex');
base64 - the compact string. 44 characters for SHA-256, 28 for SHA-1. Used when size matters (URL params, JSON fields).
const base64 = crypto.createHash('sha256').update('hello').digest('base64');
Buffer (no argument or 'binary') - the raw bytes. 32 bytes for SHA-256. Used when the hash feeds into another crypto operation, or for performance-critical code that does not need a string.
const buf = crypto.createHash('sha256').update('hello').digest();
// buf is a Buffer; convert to string with buf.toString('hex') or buf.toString('base64')
Common use cases
Content-MD5 header (base64-encoded MD5 of the body):
const md5 = crypto.createHash('md5').update(body).digest('base64');
res.setHeader('Content-MD5', md5);
ETag (often the hex-encoded MD5 or SHA-1 of the resource):
const etag = crypto.createHash('md5').update(resource).digest('hex');
res.setHeader('ETag', `"${etag}"`);
Password hashing (use bcrypt or scrypt, NOT raw SHA):
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12);
HMAC for message authentication:
const hmac = crypto.createHmac('sha256', secret).update(message).digest('hex');
Streaming large data
For a file or large stream, do not load the whole thing into memory:
const fs = require('fs');
const hash = crypto.createHash('sha256');
const stream = fs.createReadStream('big-file.bin');
stream.on('data', (chunk) => hash.update(chunk));
stream.on('end', () => {
console.log(hash.digest('hex'));
});
The team that hashes a 10 GB file uses this pattern - the alternative (loading the whole file into memory) is a 10 GB memory spike.
Common gotchas
-
Calling digest twice - the second call returns an empty hash. The
digestcall finalizes the hash object; subsequentupdatecalls do nothing. The team that has a function that hashes twice by mistake has a different output the second time. -
Mixing encodings - if you hash with
'hex'and compare against a base64 string, you get a false negative. The team that has a hash mismatch in production has an encoding mismatch. -
Using SHA-1 or MD5 for security - SHA-1 and MD5 are broken for collision resistance. The team that uses them for security (passwords, signatures) is vulnerable. Use SHA-256 or SHA-3 for new code, bcrypt/scrypt/argon2 for passwords.
-
Not specifying the algorithm -
crypto.createHash()without an algorithm throws. Always specify:'sha256','sha512', etc.
FAQ
What is the default encoding for digest?
No encoding - it returns a Buffer. The team that wants a string passes ‘hex’ or ‘base64’ to digest().
What is the difference between SHA-1, SHA-256, and SHA-512?
All are hash functions. SHA-1 is broken (collisions have been found) - do not use it for security. SHA-256 is the modern default. SHA-512 is faster on 64-bit CPUs but produces a 128-character hash. The team that needs a security-grade hash uses SHA-256 or SHA-3.
Can I use digest to hash a password?
No - SHA-256 is fast, which makes it easy to brute-force. The team that hashes passwords uses bcrypt, scrypt, or argon2 (slow by design).
Why is my hash output different in Node and Python?
Encoding mismatch. The team that uses 'hex' in Node and the equivalent in Python gets the same output; mixing encodings gives different strings. Both languages default to binary/bytes for crypto operations.
Can I hash a binary file with digest?
Yes - crypto.createHash('sha256').update(buffer).digest('hex'). The team that hashes files uses the streaming version (multiple update calls) to avoid loading large files into memory.
How this fits the rest of the stack
For a sense of what the full project costs before it commits, the RunxBuild hosting calculator shows the line items together. The API, the database, the storage, the worker, the bandwidth - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers.
Useful related references: