Modern C++17 implementation of AES-128 with CBC mode, PKCS#7 padding, and a practical command-line interface. The project keeps the cryptographic core, padding logic, CBC chaining, and tooling separated so that each piece is easy to study, test, and reuse.
- AES-128 block cipher with full round-key expansion
- ECB single-block helpers and NIST SP 800-38A test vectors
- CBC mode with PKCS#7 padding for arbitrary-length payloads
- CLI utility (
blockcrypt) for file/stream encrypt–decrypt workflows - Catch2-based unit tests: edge cases, fuzzing, standards compliance
- Optional benchmarking targets to measure ECB/CBC latency & throughput
BlockCrypt/
├── CMakeLists.txt # Root CMake project (enables AddressSanitizer by default)
├── include/ # Public headers shared by the library & CLI
│ ├── CBC.hpp
│ ├── blockcrypt.hpp
│ └── padding.hpp
├── src/ # AES core, CBC mode, padding implementations
│ ├── CBC.cpp
│ ├── blockcrypt.cpp
│ └── padding.cpp
├── constants/ # S-Boxes, inverse S-Boxes, Rcon values
│ ├── BlockCryptConstants.cpp
│ └── BlockCryptConstants.hpp
├── tests/ # Catch2 tests & benchmarks (registered with CTest)
│ ├── CMakeLists.txt
│ ├── benchmark_performance.cpp
│ └── test_blockcrypt.cpp
├── third_party/ # Vendored Catch2 headers (used when FetchContent is skipped)
├── main.cpp # Command-line entry point
├── README.md # You are here
├── LICENSE
└── build/ # (generated) CMake build tree
- C++17 compliant compiler (GCC ≥ 9, Clang ≥ 10, MSVC ≥ 2019)
- CMake ≥ 3.15
ℹ️ The root
CMakeLists.txtenables AddressSanitizer (-fsanitize=address) and debug symbols by default. To change this, pass your own compiler flags or configure a Release build (-DCMAKE_BUILD_TYPE=Release).
cmake -S . -B build
cmake --build buildcd build
# Full suite (includes long-running benchmarks ~12 minutes total)
ctest --output-on-failure --timeout 1200
# Skip benchmarks to keep CI fast
ctest -LE benchmark --output-on-failurecd build
ctest -L benchmark --output-on-failure --timeout 600The blockcrypt executable reads/writes binary data either from files or via standard streams. Keys and IVs must be provided as hexadecimal strings (32 hex characters → 16 bytes). Defaults:
- Key: 16 zero bytes (if
-k/--keyomitted) - IV:
000102030405060708090A0B0C0D0E0F(if-i/--ivomitted)
# Quick help
./build/blockcrypt --help
# Encrypt file → ciphertext.bin
./build/blockcrypt encrypt \
-k 2b7e151628aed2a6abf7158809cf4f3c \
-i 000102030405060708090a0b0c0d0e0f \
-I plaintext.bin \
-O ciphertext.bin
# Decrypt file → decrypted.bin
./build/blockcrypt decrypt \
-k 2b7e151628aed2a6abf7158809cf4f3c \
-i 000102030405060708090a0b0c0d0e0f \
-I ciphertext.bin \
-O decrypted.bin
# Stream-based usage (stdin/stdout when -I / -O omitted)
cat message.txt | ./build/blockcrypt encrypt -k ... -i ... > encrypted.binErrors such as malformed hex strings, missing files, or padding inconsistencies are reported to stderr with non-zero exit codes.
#include "blockcrypt.hpp"
#include "CBC.hpp"
#include "padding.hpp"
int main()
{
BlockCrypt::Key key{
0x2B, 0x7E, 0x15, 0x16,
0x28, 0xAE, 0xD2, 0xA6,
0xAB, 0xF7, 0x15, 0x88,
0x09, 0xCF, 0x4F, 0x3C};
BlockCrypt::Block iv{
0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B,
0x0C, 0x0D, 0x0E, 0x0F};
std::vector<uint8_t> data{'H', 'e', 'l', 'l', 'o'};
BC::encryptCBC(data, key, iv); // ciphertext w/ PKCS#7 padding
BC::decryptCBC(data, key, iv); // returns to original plaintext
std::cout << std::string(data.begin(), data.end()) << '\n';
}tests/test_blockcrypt.cpp exercises the cryptographic primitives and padding helpers:
- Round-trip encrypt/decrypt for canonical and extreme (all-zero, all-0xFF) data
- Random fuzzing (100 × 100 key/block combinations)
- NIST AES-128 ECB & CBC vector compliance
- PKCS#7 padding/unpadding symmetry checks
tests/benchmark_performance.cpp registers Catch2 benchmarks used to profile ECB/CBC latency and throughput. Benchmarks are labeled with benchmark to keep them opt-in for quick test runs.
- AES Core – Implements the SubBytes, ShiftRows, MixColumns, and AddRoundKey stages exactly as described in FIPS 197. Round keys are pre-computed once per
BlockCryptinstance. - Key Expansion – Generates 11 round keys via RotWord, SubWord (S-Box), and Rcon XOR, throwing if Rcon indices overflow.
- CBC Mode – XORs each plaintext block with the previous ciphertext (or IV) before AES encryption; decryption reverses via buffering the previous ciphertext block.
- Padding –
BCPad::addPKCS7/removePKCS7implement strict PKCS#7 checks and throw on malformed padding, preventing truncated or tampered ciphertext from silently passing.
- FIPS 197 — Advanced Encryption Standard
- NIST SP 800-38A — Recommendation for Block Cipher Modes of Operation
- AES on Wikipedia
This project is licensed under the MIT License. See LICENSE for details.
Questions, feedback, ideas?