Projects/Build SHA-256 from Scratch

Build SHA-256 from Scratch

Implement SHA-256 the way FIPS 180-4 defines it, one exact value at a time. Every lesson is a concrete spec with pinned hex: modular addition wrapping at 2^32, ROTR versus SHR giving different results on the same word, Ch and Maj bit by bit, padding that forces a whole extra block at the 448-mod-512 boundary, the message schedule words for the "abc" block, the working-variable state after round 0 and round 63, and the finished digests - empty string e3b0c442..., "abc" ba7816bf..., and a two-block message - each checked against the published standard.

25 lessonsSmall~20 min / lessonSHA-256Merkle-DamgardBitwise operations
The project

What you'll build over the next 25 lessons

Over 25 lessons you build SHA-256 from scratch on fixed-width 32-bit words, calling no crypto library for the taught computation. Every intermediate value is a fixed number defined by the standard, so the whole thing is exactly testable: you check each step against FIPS 180-4 and RFC 6234, and the finished digests against the official test vectors. The deliverable is a small library that hashes a byte string to a 32-byte digest and its hex, correct against the published vectors, plus an optional HMAC-SHA256.

You start with the arithmetic SHA-256 is built from: addition modulo 2^32, rotate-right (ROTR) versus shift-right (SHR) and why they differ, the logical functions Ch and Maj, and the four sigma functions that combine rotations and shifts. Then you pin the constants (the eight initial hash values from square roots of primes, the 64 round constants from cube roots), pad a message to a multiple of 512 bits and split it into 64-byte blocks, expand each block into a 64-word message schedule, and run the 64-round compression function - pinning the working-variable state after round 0 and after round 63 for the "abc" block. Finally you drive multiple blocks to a full digest, reproduce the empty-string, "abc", and two-block vectors exactly, and add HMAC-SHA256 against a known RFC 4231 vector.

This is an educational implementation, correct against the standard test vectors but deliberately not hardened: it is not constant-time and makes no attempt at side-channel resistance, so it is for learning how SHA-256 works, not for protecting real secrets. It is also worth remembering that SHA-256 is a one-way hash, not encryption - it produces a fixed 256-bit fingerprint and there is nothing to decrypt. What you get is the honest core of a real hash function - the same word primitives, padding, schedule, and compression that ship inside every production SHA-256 - built to match the standard byte for byte.

build-sha-256 / lesson-01.md
Lesson 01 · 32-bit word primitives

Addition modulo 2^32

SHA-256 does all of its arithmetic on 32-bit words that wrap around instead of overflowing. Today you build the one operation the whole algorithm leans on - adding two words modulo 2^32 - and pin what happens exactly at the wrap boundary.

The goal

Add two 32-bit words so the result always stays within 32 bits, wrapping past the top.

Start here - the target
TO DO
Scenario: Word addition wraps at 2^32
Giventwo 32-bit words, treated as unsigned values in the range 0 to 0xffffffff
Whenthey are added modulo 2^32
ThenAdd32(0xffffffff, 0x00000001) is 0x00000000 (it wraps to zero, it does not become 0x100000000)
AndAdd32(0x12345678, 0x11111111) is 0x23456789 and Add32(0x90000000, 0x90000000) is 0x20000000
Background

Every number SHA-256 touches is a 32-bit word, and every addition it does is modulo 2^32 - the result is kept to its low 32 bits and any carry out of the top is thrown away. This is not an accident of implementation; the standard defines it this way, so 0xffffffff + 1 is 0, not 0x100000000. Getting this one rule right is what makes every later intermediate value come out to the exact number the standard predicts.

If your language has fixed-width unsigned 32-bit integers this wrapping is free. If it does not (many do not - they use arbitrary-precision or 64-bit floats), you must mask the result with & 0xffffffff after each add, or the carry leaks upward and every downstream value drifts. Pin the boundary now: adding at the very top (0xffffffff + 1) must land back at 0, and two large words that together exceed 2^32 keep only the low 32 bits.

Make it work
// keep everything inside 32 bits; the top carry bit is discarded
func Add32(a, b uint32) uint32 {
// a uint32 already wraps on overflow in Go; other languages
// may need an explicit & 0xffffffff after the add
return a + b
}
CheckpointDONE
You can add two words with SHA-256's wrap-around arithmetic. Commit and stop here.
Scope & extensions

Where this project stops - and where to go next

The finished program correctly hashes stdin, files, or a demo string via SHA-256 or HMAC-SHA256 using the from-scratch library, but it is an educational, non-constant-time implementation with no streaming API and no SHA-224/384/512 support.

Extend it next
  • Add a streaming Writer-style API (New / Write / Sum) so large files can be hashed without loading them fully into memory
  • Add a check mode to the CLI that verifies a file against an expected digest
  • Implement SHA-224 and SHA-384/512 alongside SHA-256 to build out the full SHA-2 family
  • Add HKDF (RFC 5869) on top of the existing HMAC function
  • Add fuzz tests around padding and hashing for edge cases beyond the fixed vector suite
  • Add benchmarks to quantify the performance gap versus a hardened, constant-time implementation
Recommended reading

Books & references that go deeper

  • FIPS 180-4: Secure Hash Standard (SHS) · National Institute of Standards and Technology

    The authoritative specification. Section 4 defines the logical functions and constants, section 5 the padding and parsing, section 6.2 the SHA-256 algorithm - this project follows its structure and notation directly.

  • The IETF companion to FIPS 180-4, with reference C code for the whole SHA family and HMAC. Handy when you want a second, cross-checkable statement of the same algorithm.

  • The "Pseudocode" section is a compact, readable statement of the initial values, round constants, message schedule, and compression loop - the fastest way to sanity-check an intermediate value against the spec.

  • RFC 2104: HMAC - Keyed-Hashing for Message Authentication · Hugo Krawczyk, Mihir Bellare, Ran Canetti

    Defines HMAC in terms of any hash function - the inner and outer padding and the key handling the bonus lesson implements on top of your SHA-256.

  • The official HMAC-SHA256 test vectors. The capstone and the HMAC lesson check against these exact values.

  • Cryptography Engineering · Niels Ferguson, Bruce Schneier, Tadayoshi Kohno

    Chapter 5 on hash functions covers the Merkle-Damgard construction, length-extension, and why a raw hash is not a message authentication code - the context that motivates HMAC and the caveats on this implementation.