Key Takeaways
- →SHA-256 (Secure Hash Algorithm 256-bit) was published by NIST in FIPS 180-2 in 2001 and revised in FIPS 180-4 (2015). It produces a 256-bit hash value (64 hexadecimal characters) from input data of any length. SHA-256 processes input in 512-bit blocks through 64 rounds of compression, using six logical functions (Ch, Maj, Σ0, Σ1, σ0, σ1) defined in FIPS 180-4 §4.1.2. The compression function operates on eight 32-bit working variables (a, b, c, d, e, f, g, h) initialized from fractional parts of square roots of the first eight primes.
- →SHA-256 is the dominant cryptographic hash function in 2024: it secures Bitcoin (double-SHA-256 for mining), TLS 1.3 (HMAC-based key derivation), SSH (HMAC-SHA-256 for integrity), JWT signatures (HS256 algorithm), TLS certificates (signatures in X.509 v3), and password hashing (PBKDF2-HMAC-SHA256 with 600,000+ iterations per OWASP 2023). SHA-256 is also the algorithm behind Git LFS content addressing and Docker layer integrity verification.
- →The Web Crypto API (W3C, 2017) provides browser-native SHA-256 computation via crypto.subtle.digest('SHA-256', data), returning an ArrayBuffer digest. This eliminates the need for JavaScript SHA-256 libraries and works in all modern browsers. For HMAC-SHA-256, crypto.subtle.sign('HMAC', key, message) implements the HMAC construction per RFC 2104. The Web Crypto API is the only cryptographically-secure browser API for hash computation.
- →SHA-256 is collision-resistant as of 2024 — no practical collision attack has been published. The best known collision attack against SHA-256 requires 2^128 operations (the birthday bound). By comparison, MD5's collision attack requires 2^39 operations (trivially feasible) and SHA-1's collision attack (published by Google in 2017 as SHAttered) requires 2^63 operations. SHA-256's security margin is sufficient for all current and foreseeable cryptographic applications.
- →HMAC-SHA-256 (Hash-based Message Authentication Code) uses SHA-256 as a building block for message authentication. The HMAC construction is defined in RFC 2104 (1997): HMAC(K, m) = SHA-256((K ⊕ opad) || SHA-256((K ⊕ ipad) || m)), where ipad and opad are fixed constants. HMAC-SHA-256 is widely used for API request signing, JWT tokens, and webhook verification — any scenario where both message integrity and authenticity are required, not just integrity.
SHA-256: The 256-Bit Hash That Powers Modern Cryptography
In August 2001, NIST published FIPS 180-2, introducing SHA-256 as the primary member of the SHA-2 family. By 2024, SHA-256 is the most widely deployed cryptographic hash function in history: it secures Bitcoin's proof-of-work, signs TLS certificates, authenticates SSH sessions, derives encryption keys for TLS 1.3, anchors Docker container integrity, and verifies software package signatures. Unlike its predecessor MD5 (broken since 2004) and SHA-1 (broken since 2017's SHAttered attack), SHA-256 has no known practical collision attack as of 2024 — its security margin remains sufficient for all foreseeable cryptographic needs.
Table of Contents
- The SHA-256 Algorithm Structure
- The 64-Round Compression Function
- SHA-256 vs MD5 vs SHA-1
- HMAC-SHA-256: Authenticated Hashing
- Web Crypto API: Browser-Native SHA-256
- Frequently Asked Questions
The SHA-256 Algorithm Structure
SHA-256 follows the Merkle-Damgård construction: input is padded to a multiple of 512 bits, then processed block-by-block through a compression function that maintains a running 256-bit state.
Padding stage:
The input message is padded with a single 1 bit followed by 0 bits until the length is congruent to 448 modulo 512 bits. The remaining 64 bits encode the original message length in bits as a 64-bit big-endian integer.
Input: "hello" (40 bits / 5 bytes)
Padded: "hello" + 0x80 + (407 zero bits) + (40 as 64-bit BE) = 512 bits total
For inputs exceeding 2^64 bits (impractical but theoretically possible), the high 64 bits of length are encoded in an additional block.
Initial hash values (H[0] through H[7]):
SHA-256 starts with eight 32-bit constants derived from the fractional parts of the square roots of the first eight prime numbers:
H[0] = 0x6a09e667
H[1] = 0xbb67ae85
H[2] = 0x3c6ef372
H[3] = 0xa54ff53a
H[4] = 0x510e527f
H[5] = 0x9b05688c
H[6] = 0x1f83d9ab
H[7] = 0x5be0cd19
These constants are specified in FIPS 180-4 §5.3.3 and are identical across all SHA-256 implementations.
Round constants (K[0] through K[63]):
Each of the 64 rounds uses a different 32-bit constant derived from the fractional parts of the cube roots of the first 64 prime numbers. The constants are specified in FIPS 180-4 §4.2.2 and provide cryptographic diversity per round.
The 64-Round Compression Function
Each 512-bit block runs through 64 rounds of the compression function. The function uses eight working variables (a, b, c, d, e, f, g, h) initialized from the current hash state, then updates them in each round.
Per-round computation:
T1 = h + Σ1(e) + Ch(e, f, g) + K[i] + W[i]
T2 = Σ0(a) + Maj(a, b, c)
h = g
g = f
f = e
e = d + T1
d = c
c = b
b = a
a = T1 + T2
Logical functions:
| Function | Definition | Purpose |
|---|---|---|
| Ch(x, y, z) | (x ∧ y) ⊕ (¬x ∧ z) | Choice — selects y if x is set, z otherwise |
| Maj(x, y, z) | (x ∧ y) ⊕ (x ∧ z) ⊕ (y ∧ z) | Majority — outputs the majority value |
| Σ0(x) | ROTR^2(x) ⊕ ROTR^13(x) ⊕ ROTR^22(x) | Big sigma 0 (uppercase) |
| Σ1(x) | ROTR^6(x) ⊕ ROTR^11(x) ⊕ ROTR^25(x) | Big sigma 1 (uppercase) |
| σ0(x) | ROTR^7(x) ⊕ ROTR^18(x) ⊕ SHR^3(x) | Small sigma 0 (lowercase) |
| σ1(x) | ROTR^17(x) ⊕ ROTR^19(x) ⊕ SHR^10(x) | Small sigma 1 (lowercase) |
Message schedule expansion:
The 16 input words W[0]..W[15] (each 32 bits, derived from the 512-bit block) are extended to 64 words W[0]..W[63] via:
W[i] = σ1(W[i-2]) + W[i-7] + σ0(W[i-15]) + W[i-16] for i = 16 to 63
This schedule propagation ensures that each input bit influences the entire output within a few rounds.
Output:
After all 64 rounds, the eight working variables are added to the initial hash state:
H'[i] = H[i] + (a, b, c, d, e, f, g, h)[i] for i = 0 to 7
The final hash is the concatenation H'[0] || H'[1] || ... || H'[7], producing 256 bits (64 hex characters).
SHA-256 vs MD5 vs SHA-1
| Property | MD5 | SHA-1 | SHA-256 |
|---|---|---|---|
| Digest size | 128 bits | 160 bits | 256 bits |
| Block size | 512 bits | 512 bits | 512 bits |
| Rounds | 64 (4×16) | 80 (1×80) | 64 (1×64) |
| Collision resistance | 2^18 (broken, Wang 2004) | 2^63 (broken, SHAttered 2017) | 2^128 (secure as of 2024) |
| Pre-image resistance | 2^128 (theoretical) | 2^160 | 2^256 |
| Speed (1 GB, x86-64) | ~350 MB/s | ~250 MB/s | ~150 MB/s (without SHA-NI) |
| Speed (1 GB, with SHA-NI) | n/a | n/a | ~500 MB/s |
| Status | Legacy, non-security only | Deprecated by NIST 2011 | Recommended (FIPS 140-3) |
| Hardware acceleration | None | None | SHA-NI (Intel/AMD since 2013) |
The performance gap has narrowed dramatically with hardware acceleration. Modern Intel and AMD CPUs include dedicated SHA-256 instructions (SHA256MSG1, SHA256MSG2, SHA256RNDS2) that make SHA-256 nearly as fast as MD5 for many workloads.
HMAC-SHA-256: Authenticated Hashing
HMAC (Hash-based Message Authentication Code) constructs a message authentication code using SHA-256 as a building block. Unlike plain SHA-256 (which only provides integrity), HMAC-SHA-256 provides both integrity and authenticity — only parties with the shared secret key can produce valid HMACs.
Construction (RFC 2104):
HMAC-SHA-256(K, m) = SHA-256((K ⊕ opad) || SHA-256((K ⊕ ipad) || m))
Where:
Kis the secret key (padded to 512 bits by appending zeros if shorter than 64 bytes, or hashed if longer)opad=0x5crepeated 64 timesipad=0x36repeated 64 times||denotes byte concatenationmis the message
Common use cases:
| Use Case | Algorithm | Why HMAC |
|---|---|---|
| JWT (JSON Web Tokens) | HS256 | Stateless session tokens — receiver validates HMAC without DB lookup |
| AWS Signature V4 | HMAC-SHA-256 over canonical request | API request authentication |
| Stripe webhook signature | HMAC-SHA-256 of payload + timestamp | Verify webhook came from Stripe |
| Slack request signing | HMAC-SHA-256 | Verify Slack request authenticity |
| GitHub webhook | HMAC-SHA-256 | Verify GitHub webhook origin |
Example: AWS Signature V4 signing key derivation
def sign(key, msg):
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
# kDate = HMAC(("AWS4" + secret_key).encode("utf-8"), datestamp)
# kRegion = HMAC(kDate, region)
# kService = HMAC(kRegion, service)
# kSigning = HMAC(kService, "aws4_request")
# signature = HMAC(kSigning, string_to_sign)
Web Crypto API: Browser-Native SHA-256
The Web Crypto API (W3C, 2017) provides SHA-256 computation in all modern browsers without third-party libraries.
Basic SHA-256 hashing (JavaScript):
async function sha256Hex(text) {
const data = new TextEncoder().encode(text);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
const hash = await sha256Hex("hello");
// hash = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
HMAC-SHA-256 with Web Crypto:
async function hmacSha256Hex(secret, message) {
const keyData = new TextEncoder().encode(secret);
const key = await crypto.subtle.importKey(
"raw", keyData,
{ name: "HMAC", hash: "SHA-256" },
false, ["sign"]
);
const msgData = new TextEncoder().encode(message);
const sig = await crypto.subtle.sign("HMAC", key, msgData);
return Array.from(new Uint8Array(sig))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
const mac = await hmacSha256Hex("my-secret", "payload");
// mac = "e8b9c8d8a4c1... (64 hex chars)"
Browser support: Chrome 37+, Firefox 34+, Safari 11+, Edge 12+. Available in secure contexts only (HTTPS or localhost).