Two kinds of hashing, do not mix them up
Before any code, the most important thing about hashing in 2026: there are two completely different problems people call βhashingβ, and the same algorithm cannot solve both.
Data hashing is for content fingerprints, integrity checks, equality comparisons. You want a short, unique tag for some bytes. The right tool is a fast hash like SHA-256. Faster equals better, because you might hash a lot of data.
Password hashing is for storing user credentials. You want to prove someone knows the password without storing the password itself. The right tool is a slow hash like bcrypt or Argon2. Slower equals better, because attackers will be trying billions of guesses.
If you use SHA-256 for passwords, you have rebuilt a leaky vault. The Web Crypto API is for the first kind of problem, not the second. We covered the password side in hash passwords correctly in 2026.
The rest of this post is about the first kind. When you want to hash a file, a JWT payload, an API response, or anything else for fingerprint purposes.
The minimal example
Hashing a string with Web Crypto is three lines:
async function sha256(str) {
const bytes = new TextEncoder().encode(str);
const hash = await crypto.subtle.digest("SHA-256", bytes);
return Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
await sha256("hello world");
// "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
The first line turns the string into bytes (TextEncoder always uses UTF-8 in browsers). The second line hashes the bytes and gives back an ArrayBuffer. The third line converts the buffer to a hex string.
If you just want the hash of something without writing code, paste it into the hash generator on AldeaCode. It runs in your browser, no upload.
Hashing a file
For files, you read the bytes from a File object the user dropped or selected:
async function hashFile(file) {
const buffer = await file.arrayBuffer();
const hash = await crypto.subtle.digest("SHA-256", buffer);
return Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
const fileInput = document.querySelector("input[type=file]");
fileInput.addEventListener("change", async () => {
console.log(await hashFile(fileInput.files[0]));
});
This works up to a few hundred megabytes of file. For very large files, you would stream the bytes through the hash, but Web Crypto does not have a streaming API for digest yet. If you need it, the workaround is to chunk manually with a separate hashing library.
Picking the algorithm
Web Crypto supports SHA-1, SHA-256, SHA-384 and SHA-512.
SHA-256 is the default for almost everything. It is fast, secure for the foreseeable future, and produces a 32 byte (64 hex character) output that fits comfortably in URLs and JSON.
SHA-384 and SHA-512 are slightly slower and produce longer hashes. Use them if you have a reason. SHA-512 is sometimes faster than SHA-256 on 64 bit hardware because of how the inner math works, but in browser benchmarks SHA-256 is usually plenty fast.
SHA-1 is in the API for legacy reasons (verifying signatures from old systems). Do not use it for new code. Even Git is migrating away from it.
MD5 is not in the API at all. The browser refuses to expose it because it is broken. If you need to verify an MD5 from a legacy system, you load a library, but you should avoid building anything new that relies on MD5.
Common patterns that come up
Comparing two strings without sending them. Hash both, compare hashes. If the hashes match, the strings are the same. If they do not, the strings are different. Useful when the strings are big or sensitive.
Verifying a file did not get corrupted. Server publishes a hash, client downloads the file, hashes it, compares. If the hashes match, the file arrived intact. The hash generator is exactly this kind of tool.
Building a content addressable cache. Hash the content, use the hash as the key. Same content always gets the same key, different content gets a different key. The basis of how Git, IPFS and a lot of build tools work.
Versioning a config blob. Hash the JSON, store the hash, recompute on deploy, compare. If the hashes match, nothing changed. The JSON formatter is useful here to canonicalize the JSON before hashing, otherwise whitespace differences create different hashes.
What Web Crypto cannot help with
A few things that look hashing-shaped but are not the right job for Web Crypto:
Hash a password. Already covered. Use Argon2 or bcrypt server side.
HMAC for message authentication. Web Crypto does HMAC, but it is crypto.subtle.sign() with a key, not digest(). Different operation, different API path. Useful for cookies, tokens, anything where you need to detect tampering.
Verify a JWT signature. Possible with Web Crypto if you have the public key, but most apps verify JWTs server side, not in the browser. The browser side is for inspection only, see JWT debugging in 30 seconds.
Encrypt a long lived secret in the browser. Web Crypto can encrypt, but the question is where the key lives. We covered the key storage problem in the Web Crypto API explained.
The hash generator, the base64 encoder and the JWT decoder on AldeaCode all use Web Crypto under the hood. They run in your browser, with no data sent anywhere. Three rules: pick SHA-256 unless you have a reason, never use a fast hash for passwords, and remember that the hash proves equality, not identity.