Hash a string in JavaScript with SHA-256 (Web Crypto)
A snip.tools guide · runs alongside the Hash generator
Prefer to just run it? Use the free Hash generator — no install, in your browser.
Open the Hash generatorA cryptographic hash turns any input into a fixed-size fingerprint: the same input always yields the same output, but you can't work backwards from the hash to the input, and changing a single byte scrambles the whole result. JavaScript can do this natively through the Web Crypto API — no library needed. Here's the SHA-256 one-liner, how to format the result, and the one rule that matters most: never use a plain hash for passwords. (To hash a value right now without writing any code, use our in-browser hash generator.)
SHA-256 with crypto.subtle.digest
The hashing entry point is crypto.subtle.digest. It takes an algorithm name and a buffer of
bytes, and returns a promise of an ArrayBuffer — the raw digest. Two small conversions bookend
it: text to UTF-8 bytes going in, and bytes to a hex string coming out.
// Hash a string with SHA-256 using the built-in Web Crypto API.
async function sha256(message) {
const data = new TextEncoder().encode(message); // string → UTF-8 bytes
const hash = await crypto.subtle.digest('SHA-256', data); // → ArrayBuffer
return [...new Uint8Array(hash)] // bytes → hex string
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
await sha256('hello');
// "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
It's asynchronous because the API is designed to allow off-thread and hardware-accelerated
implementations, so you'll always await it. And like all of Web Crypto, it's only available in a
secure context: https:// pages and localhost, but not plain
http://.
The hex conversion, line by line
The digest is raw bytes; humans want hex. new Uint8Array(hash) views those bytes, each
b.toString(16) renders one byte as hex, and padStart(2, '0') keeps the leading zero
on values below 16 (so byte 5 is "05", not "5") — drop that and your
hash silently comes out the wrong length. Joining gives the familiar 64-character SHA-256 string.
Other digest sizes
The same function does the rest of the SHA-2 family — just change the name:
await crypto.subtle.digest('SHA-256', data); // 64 hex chars
await crypto.subtle.digest('SHA-384', data); // 96 hex chars
await crypto.subtle.digest('SHA-512', data); // 128 hex chars
// 'SHA-1' is also supported but is broken — see below. Use SHA-256 unless you have a specific reason for a longer digest. Note that MD5 is not available in Web Crypto at all, by design — it's considered unsafe (our hash generator implements MD5 separately for the legacy-checksum use case, but you shouldn't reach for it in new code).
Verifying a value against a known hash
A common task is checking whether some input matches a stored fingerprint — file integrity, a checksum, a cache key. Hash the input and compare hex strings:
// Verify a value against a known hash by comparing the hex strings.
async function matches(input, expectedHex) {
return (await sha256(input)) === expectedHex.toLowerCase();
} Why you must never hash passwords this way
This is the mistake that turns up in breach post-mortems. SHA-256 is built to be fast — modern hardware computes billions per second — which is exactly what an attacker with a leaked database wants. A plain (or even salted) SHA-256 of a password is brute-forceable. Passwords need a deliberately slow, salted, memory-hard function:
// DON'T do this for passwords — plain SHA-256 is far too fast.
const stored = await sha256(userPassword); // ❌ brute-forceable
// DO use a slow, salted password hash on the server (Argon2id / scrypt /
// bcrypt). Web Crypto's PBKDF2 is the browser-available option:
async function pbkdf2(password, saltBytes, iterations = 600_000) {
const key = await crypto.subtle.importKey(
'raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits'],
);
const bits = await crypto.subtle.deriveBits(
{ name: 'PBKDF2', salt: saltBytes, iterations, hash: 'SHA-256' }, key, 256,
);
return new Uint8Array(bits);
} The right answer for passwords is Argon2id (or scrypt/bcrypt) on the server. In the browser, Web Crypto offers PBKDF2 with a high iteration count, shown above. The general rule: SHA-256 is for integrity and fingerprints, not for protecting secrets.
Hashing is one-way — and not encryption
Finally, keep the categories straight. Hashing is irreversible by design: there's no "unhash". That's different from Base64 (reversible encoding, hides nothing) and from encryption (reversible with a key). If you need to read the data back, you want encryption, not a hash. To generate hashes interactively across MD5, SHA-1 and the SHA-2 family, the hash generator runs entirely in your browser — your input is never uploaded. Writing your own randomness or IDs nearby? See generating a UUID in JavaScript, which draws from the same Web Crypto module.
Frequently asked questions
- How do I hash a string with SHA-256 in JavaScript?
- Encode the string to bytes with new TextEncoder().encode(str), pass the bytes to await crypto.subtle.digest('SHA-256', bytes), then map the resulting ArrayBuffer to a hex string. It's built into the browser and Node — no library needed.
- Why is crypto.subtle.digest asynchronous?
- The Web Crypto API is designed to allow off-thread and hardware-accelerated implementations, so digest() returns a promise — you always await it. There is no synchronous equivalent in the browser.
- Can I compute an MD5 hash with Web Crypto?
- No. Web Crypto deliberately omits MD5 (and offers SHA-1 only for legacy interop) because both are considered unsafe. Use SHA-256 for new code; reach for MD5 only for non-security checksums, and via a separate implementation.
- Is SHA-256 safe for hashing passwords?
- No. SHA-256 is built to be fast, which is exactly what an attacker with a leaked database wants — a plain or even salted SHA-256 of a password is brute-forceable. Use a slow, salted function: Argon2id, scrypt or bcrypt on the server, or PBKDF2 with a high iteration count in the browser.
- Why does my SHA-256 hash come out the wrong length?
- Almost always a missing padStart(2, "0") in the hex conversion. Bytes below 16 render as a single hex digit, so byte 5 becomes "5" instead of "05" and the string ends up shorter than 64 characters. Pad each byte to two digits.
- Does hashing work on http pages?
- No. Like all of Web Crypto, crypto.subtle.digest is only available in a secure context: https:// pages and localhost, but not a plain http:// origin.
Try it now: the Hash generator does everything in this guide in your browser — nothing is uploaded. Browse more guides or the full tool list.