← Back to the tool

Foundations · Under the hood

SHA-256 & the avalanche effect

What each button in the tool actually computes, step by step — with the real formulas.

From text to bytes

Whatever you type is first converted to UTF-8 bytes — a real difference from "characters": an emoji or accented letter can take up several bytes, and SHA-256 hashes those bytes, not an abstract idea of "characters".

The actual WebCrypto call
const bytes = new TextEncoder().encode(text); // UTF-8, not UTF-16

const digest = await crypto.subtle.digest("SHA-256", bytes);
// digest is a 32-byte ArrayBuffer — hex-encoded above for display

digest() takes just an algorithm name and the bytes — there's no key, no options object. "SHA-256" is the only string this tool passes; WebCrypto also accepts "SHA-1", "SHA-384", and "SHA-512".

Padding to a whole number of blocks

SHA-256 only processes complete 512-bit (64-byte) blocks. Your message is padded: a single 1 bit is appended, then as many 0 bits as needed, then the original bit-length as a 64-bit number — leaving a result that's an exact multiple of 512 bits. For a short string like this tool's defaults, that's a single block.

No separate call — it runs inside the same digest()
// Same line as above — padding isn't something the caller does.
const digest = await crypto.subtle.digest("SHA-256", bytes);

WebCrypto never exposes a raw "add padding" or "process block" step. You hand digest() the unpadded message and get the final hash back; the padding and block-splitting happen natively, out of JavaScript's reach.

len(message)+1+zeros+64≡0(mod512)\text{len}(\text{message}) + 1 + \text{zeros} + 64 \equiv 0 \pmod{512}

64 rounds of mixing

That padded block runs through 64 rounds of bitwise rotation, shifting, and modular addition, updating eight 32-bit working variables derived from SHA-256's fixed initial constants. The result becomes the chaining value fed to the next block — the Merkle-Damgård construction covered in the Hash functions module — or, since there's only one block here, the digest itself.

No separate call — also inside the same digest()
// Still the same line — the 64 compression rounds are the bulk of
// what happens between calling digest() and its promise resolving.
const digest = await crypto.subtle.digest("SHA-256", bytes);

This is why SHA-256 is a single async call rather than a class you instantiate and feed chunks to (compare Node's crypto.createHash, which does expose an update() step) — the browser API only gives you the final answer.

Why editing one character changes everything

Each of those 64 rounds is designed to spread every input bit's influence across the entire internal state within a handful of rounds, the same avalanche principle behind AES. Change a single UTF-8 byte at the very start, and by round 64 essentially every one of the 256 output bits has an even chance of flipping — exactly what you see comparing the two digests above.

Now try it

Back to SHA-256 & the avalanche effect →

Hash any text live and watch the digest change completely when you edit a single character.