Hash strings in Node.js: createHash, Web Crypto, and the bcrypt warning
Node.js gives you two ways to hash a string and one warning to internalise: SHA is fast and great for checksums, terrible for passwords. Pick `crypto.createHash` for the legacy API or `crypto.subtle.digest` for code that also runs in the browser.
createHash is the canonical Node API
crypto.createHash has been in Node since 0.x and is what most snippets you find on the web use. It is synchronous, fast, and supports every algorithm OpenSSL exposes (SHA-256, SHA-512, SHA-1, MD5, plus exotic ones via getHashes()).
```js import { createHash } from "node:crypto";
const digest = createHash("sha256") .update("AldeaCode", "utf8") .digest("hex"); ```
Always pass an explicit encoding to update. Default is the engine's interpretation of the buffer, which bites you the first time someone passes a string with accented characters. utf8 is what you want 99% of the time.
Web Crypto is the portable choice
Since Node 19, globalThis.crypto is the same Web Crypto API you have in the browser. It is async, returns an ArrayBuffer, and lets you ship the same code to a browser, a Cloudflare Worker, and a Node server.
const bytes = new TextEncoder().encode("AldeaCode");
const buffer = await crypto.subtle.digest("SHA-256", bytes);
const hex = [...new Uint8Array(buffer)]
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
The Web Crypto API only supports SHA-1, SHA-256, SHA-384 and SHA-512. If you need SHA-3 or BLAKE you stay on createHash. For everything else, the portable API is now the better default.
Never hash passwords with SHA
This is the rule that keeps catching people in production. SHA-256 of a password is not a hashed password. It is a one-line GPU job. A modern card grinds through billions of SHA-256 candidates per second, which means any password under 14 random chars falls in minutes.
For passwords use bcrypt, scrypt or Argon2id. Node ships scrypt natively in crypto.scrypt. For bcrypt or Argon2 install bcrypt or argon2 from npm. They are slow on purpose: cost factors are tunable so the hash takes 100 ms today and stays expensive for an attacker.
SHA is for checksums, content addressing, ETags, HMAC keys, and integrity. Different problem, different tool.
Streams for large files
If you are hashing a 4 GB video upload, do not load it into memory. createHash is a stream and pipes happily:
```js import { createReadStream } from "node:fs"; import { createHash } from "node:crypto";
const hash = createHash("sha256"); createReadStream("./video.mp4").pipe(hash).on("finish", () => { console.log(hash.digest("hex")); }); ```
The Web Crypto API has no streaming equivalent. If you need to hash multi-gigabyte files in a portable codebase, you are back on createHash whether you like it or not.
Working example
javascriptimport { createHash } from "node:crypto";
// Sync, classic Node
const sha256 = createHash("sha256")
.update("AldeaCode", "utf8")
.digest("hex");
console.log(sha256);
// Async, portable to browsers and edge runtimes
const bytes = new TextEncoder().encode("AldeaCode");
const buffer = await crypto.subtle.digest("SHA-256", bytes);
const hex = [...new Uint8Array(buffer)]
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
console.log(hex); Just need the result?
When you just need to hash a single string for a quick check, like comparing a checksum a vendor mailed you, opening a Node REPL is overkill. Paste the string into the browser-based hash generator, get SHA-1, SHA-256, SHA-384 and SHA-512 at once, and never leave the page.
Open SHA Hash Generator →Frequently asked questions
Is createHash safe to use in 2026?
Yes for SHA-256 and SHA-512. The API is stable and FIPS 180-4 compliant. Avoid MD5 and SHA-1 for any new integrity check; both have practical collision attacks.
Do I need to await crypto.subtle.digest?
Yes. Web Crypto is fully async and returns a Promise that resolves to an ArrayBuffer. If you forget the await you get a Promise object back instead of bytes.
What is the fastest hash in Node?
Hardware-accelerated SHA-256 wins on x86 with SHA-NI. createHash is faster than crypto.subtle for tight synchronous loops because there is no Promise overhead. Both are plenty fast for normal payloads.