SHA-256 Algorithm Explained Step by Step: Padding, Message Schedule and 64 Rounds

SHA-256 is defined in NIST FIPS 180-4 and is built from the same parts as MD5 (padding, a running state, a compression function applied block by block) but with a larger state, a message schedule that stretches each block from 16 to 64 words, and much stronger mixing per round. This walkthrough follows the specification in order, using the input `abc`, whose intermediate values are published by NIST as an example, and a short Python implementation that we verified against `hashlib`.

The structure in one paragraph

SHA-256 processes the message in 512-bit blocks. It keeps a state of eight 32-bit words, H0 to H7. For each block it builds a 64-word message schedule, runs 64 rounds that update eight working variables named a to h, and adds the result back into the state. After the last block, the eight state words, written big-endian, are the 256-bit digest. All additions are modulo 2³². The only operations are addition, AND, XOR, NOT, right shift and right rotation, which is why SHA-256 is fast in hardware and software alike.

Unlike MD5, SHA-256 is big-endian throughout: message words, the length field and the output are all read and written most significant byte first.

Step 1: padding

Padding works like MD5's: append a single 1 bit (the byte 0x80), then zeros until the length is 448 modulo 512 bits, then the original length in bits as a 64-bit big-endian integer. For abc (24 bits) the padded block is:

61626380 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000018
W[0] = 0x61626380 ("abc" + the 1 bit)    W[15] = 0x00000018 (24 bits)

A message of 56 bytes or more no longer leaves room for the 8-byte length in its last block, so it spills into an extra block, as in MD5. The length field caps the input at 2⁶⁴ − 1 bits, which is not a limit anyone reaches in practice.

Step 2: initial hash values and round constants

The eight initial state words are the first 32 bits of the fractional parts of the square roots of the first eight primes, 2 to 19. The 64 round constants K are the first 32 bits of the fractional parts of the cube roots of the first 64 primes, 2 to 311. Like MD5's sine table, they are derived from a public formula so that nobody can suspect a hidden choice:

from math import isqrt

first8 = [2, 3, 5, 7, 11, 13, 17, 19]
H0 = [isqrt(p << 64) & 0xFFFFFFFF for p in first8]   # exact sqrt(p) * 2^32, fractional bits
# 6a09e667 bb67ae85 3c6ef372 a54ff53a 510e527f 9b05688c 1f83d9ab 5be0cd19

# K[0..3] = 428a2f98 71374491 b5c0fbcf e9b5dba5 ... K[63] = c67178f2  (cube roots of primes)

Integer square roots give the exact bits by construction. Ordinary 64-bit floats happen to produce the same 72 constants, which we checked, but the integer form does not depend on floating-point precision at all.

Step 3: the message schedule

Each block gives 16 words, W[0] to W[15]. SHA-256 expands them to 64, so that every round has its own input word and every message bit influences many rounds. Each new word is computed from four earlier ones using two small functions, σ0 and σ1, which combine rotations and a shift:

σ0(x) = ROTR7(x)  XOR ROTR18(x) XOR SHR3(x)
σ1(x) = ROTR17(x) XOR ROTR19(x) XOR SHR10(x)

W[t] = σ1(W[t−2]) + W[t−7] + σ0(W[t−15]) + W[t−16]     for t = 16 … 63

The mix of rotations by different amounts and a plain shift is deliberate: rotation keeps all the bits, while the shift discards some, which breaks the symmetry that would otherwise make the schedule easier to analyse. For abc, W[16] happens to be 61626380 again, because nearly all the words it is built from are zero.

Step 4: 64 rounds of compression

The working variables a to h start as copies of the current state. Every round computes two temporary values and shifts the variables down by one position, inserting new values at a and e:

for t in range(64):
    S1  = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)
    ch  = (e & f) ^ (~e & g)                 # "choose": e picks bits from f or g
    T1  = (h + S1 + ch + K[t] + W[t]) & 0xFFFFFFFF
    S0  = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)
    maj = (a & b) ^ (a & c) ^ (b & c)        # "majority" of a, b, c
    T2  = (S0 + maj) & 0xFFFFFFFF
    h, g, f, e, d, c, b, a = g, f, e, (d + T1) & 0xFFFFFFFF, c, b, a, (T1 + T2) & 0xFFFFFFFF

# then: H[i] = (H[i] + working variable i) mod 2^32, for all eight words

Ch and Maj are the nonlinear parts: without them the whole function would be linear over the bits and could be solved with algebra. The large Σ functions spread each bit across the word. For abc, after round 0 the new values are a = 5d6aebcd and e = fa2a4622, the same numbers NIST lists in its worked example. Our full implementation, a few dozen lines with the padding and schedule, reproduces hashlib.sha256() for the empty string, abc, 55- and 56-byte inputs and a 1,000-byte input, which covers both one-block and two-block padding.

Step 5: the output, and what it gives you

After the last block, the eight state words are written out big-endian, 32 bytes in all, usually printed as 64 hexadecimal characters:

>>> import hashlib
>>> hashlib.sha256(b"abc").hexdigest()
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'

Notice that the first word, ba7816bf, appears directly at the start, with no byte reversal. That is the visible difference from MD5. The 64-character length never changes, as explained in SHA-256 hash length; you can compare your own inputs with a SHA-256 hash generator.

Security: what holds and what to watch for

No practical collision or preimage attack on full SHA-256 is known; published attacks work only on reduced versions with fewer rounds. NIST continues to approve it, and it underpins TLS certificates, code signing, Git's newer object format and Bitcoin.

One structural property does matter in application code: length extension. Because the digest is the complete internal state, anyone who knows SHA-256(secret + message) and the length of the secret can compute the hash of secret + message + padding + more without knowing the secret. Never build a signature as sha256(key + data); use HMAC-SHA-256, which is designed to be immune. SHA-512/256 and SHA-3 do not have this property. And as with every fast hash, SHA-256 alone is not suitable for storing passwords; use a slow password hash.

Sources and further reading

NIST FIPS 180-4 (Secure Hash Standard) defines SHA-256, its constants, functions and padding; NIST's "Cryptographic Standards and Guidelines: Examples with Intermediate Values" publishes the abc round values used above. RFC 6234 contains reference C code and test vectors for the SHA-2 family. RFC 2104 defines HMAC. The Python hashlib documentation describes sha256().

Frequently asked questions

How does the SHA-256 algorithm work?

It pads the message to 512-bit blocks, expands each block into 64 schedule words, and runs 64 rounds that mix those words into eight 32-bit state variables. After the last block, the state is output as a 256-bit digest.

How many rounds does SHA-256 have?

64 rounds per 512-bit block. Each round uses one word of the message schedule and one of the 64 round constants.

Where do the SHA-256 constants come from?

The initial hash values are the fractional parts of the square roots of the first 8 primes, and the round constants the fractional parts of the cube roots of the first 64 primes, each truncated to 32 bits.

What is the SHA-256 hash of abc?

ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad. It is the standard test vector in FIPS 180-4 and RFC 6234.

What is the difference between SHA-256 and MD5 internally?

SHA-256 has a 256-bit state instead of 128, a message schedule that expands each block to 64 words, stronger round functions and big-endian byte order. MD5 uses each message word directly and is little-endian.

Is SHA-256 vulnerable to length extension attacks?

Yes. Knowing SHA-256(secret + message) lets an attacker compute the hash of an extended message. Use HMAC-SHA-256 for keyed hashes instead of concatenating a secret and the data.