Two hashes, one family tree, one byte of difference
Keccak-256 and SHA-256 answer the same question — give me a fixed-size fingerprint of this data — and both are cryptographically secure. Yet they are not interchangeable, and confusing them is one of the most common bugs in Web3 engineering. Sign a message with the wrong one and your signature verification fails silently. Build a Merkle proof with SHA-256 when the contract expects Keccak-256 and every proof reverts.
The short version: SHA-256 is the general-purpose integrity hash of the internet — TLS certificates, Git objects, Bitcoin, file checksums. Keccak-256 is the hash of the Ethereum Virtual Machine — function selectors, address derivation, typed-data signatures, on-chain Merkle trees. And SHA3-256 is a third thing that is frequently mistaken for Keccak-256.
The gotcha that costs hours: Keccak-256 ≠ SHA3-256
The Keccak team won NIST's SHA-3 competition in 2012. When NIST standardized the design in 2015 (FIPS 202), it changed the padding byte from 0x01 to 0x06 — a deliberate domain separation tweak. The result: the same input produces different digests under original Keccak and under standardized SHA3-256.
Ethereum launched in 2015 with the pre-standard Keccak-256 already baked into the protocol — and never switched. So when an Ethereum tool says "keccak256", it means the original padding (0x01), and when your system library says "SHA3-256" (Node's crypto, Python's hashlib.sha3_256, Go's golang.org/x/crypto/sha3), it means the standardized padding (0x06). Same sponge construction, same capacity, incompatible outputs.
# same input, three different answers
keccak256("transfer(address,uint256)")
→ 0xa9059cbb2ab09eb219583f4a59a5d0623ade34bc954990bef74b0f6c6dece5be (Ethereum selector)
sha3_256("transfer(address,uint256)")
→ 0x5c1c3be2... (standardized SHA3 — NOT what the EVM uses)
sha256("transfer(address,uint256)")
→ 0x6a4ffbd9... (SHA-2 family, entirely different design)
The first four bytes of the Keccak digest — a9059cbb — are the function selector every EVM wallet and contract recognizes for ERC-20 transfer. Compute that selector with SHA3-256 and you get a selector for a function that does not exist.
When to use Keccak-256
Reach for Keccak-256 whenever the EVM is the verifier:
- Function selectors. The first 4 bytes of
keccak256(signature)route every contract call. Computing selectors, encoding calldata, or decoding transactions all need Keccak — the exact algorithm, original padding. - Address derivation. An Ethereum address is the last 20 bytes of
keccak256(uncompressed_public_key). Not the first 20, not SHA-256 — the mismatched variants produce valid-looking addresses that own nothing. - Solidity hashing. The
keccak256()builtin is the only hash most contracts use for commitments, digests, and comparisons, because it is cheap and native. - EIP-712 typed data. The signature scheme every modern wallet uses for human-readable signing hashes the typed structure with Keccak-256 at every level.
- On-chain Merkle proofs. Airdrops and whitelist trees are built with Keccak because the contract verifier is Solidity's
keccak256. Your off-chain tree builder must match, including the double-hash of leaves and theabi.encodePackedconcatenation quirks. - CREATE2 addresses. The deterministic address formula keccaks the deployer, salt, and init code together.
// JavaScript — ethers v6
import { keccak256, toUtf8Bytes } from 'ethers'
keccak256(toUtf8Bytes('transfer(address,uint256)'))
// '0xa9059cbb2ab09eb219583f4a59a5d0623ade34bc954990bef74b0f6c6dece5be'
// Solidity — the same hash on-chain
bytes32 digest = keccak256(abi.encodePacked("transfer(address,uint256)"));
When to use SHA-256
Reach for SHA-256 everywhere the ecosystem predates or ignores the EVM:
- Integrity and checksums. File verification, software signatures,
sha256sum— SHA-2 is the default everywhere precisely because it predates SHA-3 and has decades of unbroken deployment. - Bitcoin. Double SHA-256 (
SHA256(SHA256(x))) secures block headers and addresses in that order — mixing in Keccak breaks at the first verification. - TLS and certificates. Signature algorithms in the WebPKI use SHA-256 (or SHA-384) with RSA/ECDSA.
- HMAC.
HMAC-SHA256is the workhorse of API request signing and JWTs (HS256). HMAC's construction already neutralizes length-extension, which is the main theoretical weakness of raw SHA-256 — you get a keyed MAC with no caveats. - Subresource Integrity and content addressing. SRI hashes in CSP headers, Docker digests, and IPFS multihashes use SHA-2 family hashes.
// Node — SHA-256 and HMAC, no extra dependencies
import { createHash, createHmac } from 'node:crypto'
createHash('sha256').update('payload').digest('hex')
createHmac('sha256', secret)
.update('POST /api/transfer
' + timestamp + '.' + body)
.digest('base64url') // the API-signature pattern
Are they equally secure?
Yes, in every way that matters today. Both offer 128-bit collision resistance at 256-bit output; neither has a practical collision or preimage attack. Keccak's sponge construction has a wider security margin in some theoretical senses; SHA-256 has more cryptanalytic mileage. The choice is compatibility, not strength — pick the hash your verifier expects, and if you are the verifier, pick the one your ecosystem's tooling speaks natively.
One real caveat they share: both are unkeyed hashes, so both are length-extension vulnerable when used as hash(secret ‖ message). Never authenticate that way — use HMAC with either hash.
The cross-language interop table
| Language | Keccak-256 (Ethereum) | SHA-256 |
|---|---|---|
| JavaScript | ethers.keccak256(), js-sha3 keccak256 | crypto.createHash('sha256'), Web Crypto |
| Python | eth_hash, SafeKeccak, sha3.keccak_256 (not hashlib.sha3_256!) | hashlib.sha256() |
| Go | golang.org/x/crypto/sha3 with NewLegacyKeccak256() | crypto/sha256 |
| Rust | tiny-keccak keccak256 | sha2::Sha256 |
| Solidity | keccak256() builtin | precompile 0x02 |
Python's pysha3 is the classic trap: it exposed both keccak_256 and sha3_256, was deprecated, and replacements shuffle the names again. If a digest starts looking right but a contract still rejects it, print the first byte of the domain separator digest — a mismatched padding shows up immediately.
Decision table
| You are… | Use |
|---|---|
| Computing a function selector or calldata | Keccak-256 |
| Deriving an address from a public key | Keccak-256 |
| Building Merkle proofs verified by a Solidity contract | Keccak-256 (match the contract's exact encodePacked layout) |
| Signing EIP-712 typed data | Keccak-256 |
| Verifying file downloads, writing SRI, content addressing | SHA-256 |
| Anything Bitcoin | Double SHA-256 |
| Signing API requests or JWTs | HMAC-SHA256 |
| General-purpose integrity outside Web3 | SHA-256 |
Try both, right now, in your browser
The fastest way to internalize the difference is to hash the same string with each. Our Keccak-256 hasher and SHA-256 hash tool run side by side — paste transfer(address,uint256) into both and watch the digests diverge from the first byte. Then take the Keccak digest's first four bytes to the function selector calculator — a9059cbb, the selector you have seen in every Etherscan transaction. Everything runs client-side, so test signatures and candidate digests never leave your machine.