A from-scratch implementation of the SHA-256 hash function in C++, written directly against the NIST FIPS 180-4 specification. No external crypto libraries — just the algorithm as defined in the standard.
The implementation is header-only: drop sha256.hpp into your project and you're done.
- Single-header, zero-dependency (standard library only)
- Faithful to FIPS 180-4: message padding, schedule expansion, and the 64-round compression function
- Validated against the official NIST test vectors
- A small CLI for hashing files, plus a test runner
- A C++17 compiler (the
Makefileusesg++) make
make # build the CLI -> ./sha256
make test # build the test runner -> ./test
make clean # remove built binaries
make re # clean + rebuild the CLIThe build uses -std=c++17 -Wall -Wextra.
The CLI reads a file and prints its digest alongside the filename, in the same <hash> <file> style as sha256sum:
$ ./sha256 main.cpp
3a1f...c9e2 main.cppIt exits with a non-zero status and an error message if no file is given or the file can't be opened.
Include the header and call the static hash function with the data you want to digest. It returns the digest as a 64-character lowercase hex string:
#include "sha256.hpp"
#include <iostream>
int main() {
std::cout << sha256::hash("abc") << "\n";
// ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
}test.cpp checks the implementation against NIST test vectors (the empty string, "abc", the 448-bit two-block message, and the pangram with and without a trailing period). Build and run it:
make test
./testEach case reports [PASS] or [FAIL], followed by a summary. The runner exits 0 only if every vector passes.
sha256/
├── sha256.hpp # the implementation (header-only)
├── main.cpp # CLI: hash a file
├── test.cpp # NIST test vectors
├── Makefile # build targets
├── knowledge.md # study notes on the algorithm
├── NIST.FIPS.180-4.pdf # the standard
├── README.md
└── LICENSE
SHA-256 processes the message in 512-bit blocks and produces a 256-bit digest. The implementation follows the spec stage by stage:
- Padding — append a
1bit, then0bits, then the original message length as a 64-bit big-endian integer, so the total is a multiple of 512 bits. - Parsing — split the padded message into 512-bit blocks, each read as sixteen 32-bit big-endian words.
- Message schedule — expand the 16 words of each block into 64 using the lower sigma functions.
- Compression — run 64 rounds over the eight working variables with the round constants
K, theCh/Majfunctions, and the upper sigma functions. - Digest — add each block's result back into the running hash state, then emit the eight words as hex.
See knowledge.md for fuller notes on the bitwise operations, the logical functions, and why the design choices (e.g. the length field, SHR vs. ROTR) matter.
Released under the MIT License. See LICENSE for details.