C++ AES (Advanced Encryption Standard) library. Forked from SergeyBel/AES and extended with utilities, examples, CMake packaging, and CI.
Stable releases are maintained on the
stablebranch and published as semver tags.
- AES-128 / AES-192 / AES-256
- Modes: ECB, CBC, CFB, CTR, GCM
- Runtime AES-NI detection on x86/x86_64 when built with AES-NI/PCLMUL flags; software fallback otherwise
- Convenience utilities (
aes_cpp::utils) with string/std::vectorhelpers - Optional debug helpers (hex printers) behind
AESCPP_DEBUG - CMake package:
aes_cpp::aes_cpptarget,find_packagesupport
-
Key management: the library does not generate or store keys. Derive keys via a KDF (PBKDF2/scrypt/Argon2) and manage rotation/storage in your application.
-
Side channels:
- Software path is not constant-time. Even with AES-NI, side channels may remain depending on platform and usage. Evaluate your threat model (shared CPU, co-tenancy, etc.).
-
IV/nonce uniqueness is mandatory per key:
-
GCM (recommended): 12-byte IV (nonce) required; non-12-byte IVs are not supported. Never reuse an IV with the same key. Reuse breaks confidentiality and integrity.
-
GCM tag length fixed to 16 bytes.
- CTR/CFB: IV/nonce must be unique per key. A counter-based scheme is typical.
- CBC: IV must be unpredictable (CSPRNG).
-
ECB is provided for demonstration only. Do not use in real systems; it leaks patterns. Prefer authenticated encryption (GCM).
-
Zeroization: memory wiping is best-effort and does not cover OS swap or external copies.
- C++11 or newer
- CMake ≥ 3.14
git clone https://github.com/NewYaroslav/aes-cpp.git cd aes-cpp ./setup-hooks.sh # enables clang-format pre-commit (enforced by CI) cmake -S . -B build cmake --build build # enable and run tests cmake -S . -B build -DAES_CPP_BUILD_TESTS=ON cmake --build build ctest --test-dir build
Minimal compile & run (Linux, from repo root):
# Build static lib first via CMake as above, then:
g++ examples/ctr.cpp -std=c++17 -Iinclude -Lbuild -laes_cpp -o ctr_example
./ctr_example# CMakeLists.txt add_subdirectory(external/aes-cpp) # Optionally: set(AES_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE) target_link_libraries(your_app PRIVATE aes_cpp::aes_cpp)
Install to a prefix and consume via find_package:
cmake -S . -B build
cmake --install build --prefix /your/install/prefix# consumer CMakeLists.txt find_package(aes_cpp CONFIG REQUIRED) target_link_libraries(your_app PRIVATE aes_cpp::aes_cpp)
The project installs:
-
headers →
include/ -
library →
lib/ -
CMake package files →
lib/cmake/aes_cpp/aes_cppConfig.cmakeaes_cppConfigVersion.cmakeaes_cppTargets.cmake
Exported target name: aes_cpp::aes_cpp (also local alias aescpp).
With a vcpkg manifest (vcpkg.json) in the repo, typical flow:
git clone https://github.com/microsoft/vcpkg.git ./vcpkg/bootstrap-vcpkg.sh ./vcpkg/vcpkg install # default triplet ./vcpkg/vcpkg install --x-feature=tests cmake -S . -B build \ -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake \ -DVCPKG_MANIFEST_FEATURES=tests \ -DAES_CPP_BUILD_TESTS=ON cmake --build build
On Windows (PowerShell / cmd) see Windows Build.
Include headers from include/aes_cpp.
#include <aes_cpp/aes.hpp> #include <aes_cpp/aes_utils.hpp> #include <array> #include <vector> using namespace aes_cpp; int main() { const unsigned char plain[] = { 0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77, 0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff }; const unsigned char key[] = { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; auto iv = utils::generate_iv_16(); // CBC IV from CSPRNG AES aes(AESKeyLength::AES_128); // utils::encrypt/decrypt auto-apply PKCS#7 for CBC auto enc = utils::encrypt(std::vector<uint8_t>(plain, plain+sizeof(plain)), key, utils::AesMode::CBC, std::nullopt, iv); auto dec = utils::decrypt(enc, key, utils::AesMode::CBC); }
#include <aes_cpp/aes_utils.hpp> #include <array> #include <string> #include <iostream> using namespace aes_cpp; int main() { std::string text = "CTR mode example"; std::array<uint8_t,16> key { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; auto iv = utils::generate_iv_16(); // unique per (key, message) auto encrypted = utils::encrypt(text, key, utils::AesMode::CTR, std::nullopt, iv); auto restored = utils::decrypt_to_string(encrypted, key, utils::AesMode::CTR, std::nullopt); std::cout << restored << "\n"; }
Note (CTR): increments the 128-bit counter in big-endian order (J0+1, J0+2, ...); throws std::length_error on counter wrap-around (practically unreachable).
#include <aes_cpp/aes_utils.hpp> #include <array> #include <vector> using namespace aes_cpp; struct GcmPacket { std::array<uint8_t,12> iv; // 12-byte IV (nonce) std::vector<uint8_t> ciphertext; // raw bytes std::array<uint8_t,16> tag; // 16-byte auth tag }; int main() { std::string text = "GCM example"; std::array<uint8_t,16> key { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; std::vector<uint8_t> aad = { 'h','e','a','d','e','r' }; auto enc = utils::encrypt_gcm(text, key, aad); // {iv, ciphertext, tag}; do not reuse `enc.iv` with the same key // Serialize as {iv || ciphertext || tag} if desired std::vector<uint8_t> wire; wire.insert(wire.end(), enc.iv.begin(), enc.iv.end()); wire.insert(wire.end(), enc.ciphertext.begin(), enc.ciphertext.end()); wire.insert(wire.end(), enc.tag.begin(), enc.tag.end()); // Decrypt auto plain = utils::decrypt_gcm_to_string(enc, key, aad); }
GCM limits & tag. AAD ≤ 2^39−256 bytes; AAD+plaintext ≤ 2^39−256 (per spec). Implementation limit: plaintext ≤ 2^36 bytes per (key, IV) due to 32-bit block counter. Tag length is fixed to 16 bytes. On authentication failure the output is zeroized and an exception is thrown (see Errors & Exceptions).
utils::encrypt, utils::decrypt, and utils::decrypt_to_string for CBC/CFB/CTR accept an optional MAC callback. The library authenticates IV || ciphertext and passes this buffer to your callback. Use a dedicated MAC key; do not reuse the AES key.
auto mac_fn = [](const std::vector<uint8_t>& data) { return my_hmac(data); // data = IV || ciphertext }; auto encrypted = utils::encrypt(text, key, utils::AesMode::CTR, mac_fn); auto restored = utils::decrypt_to_string(encrypted, key, utils::AesMode::CTR, mac_fn);
Template Utilities Limitations
In this library, the template functions from aes_utils are declared in the header but defined in the .cpp and pre-explicitly instantiated only for a limited set of key types. This design is intentional to keep ABI stable and avoid ODR conflicts across translation units. Therefore, only the pre-supported key types (listed below) are guaranteed to work.
If you call, for example, encrypt(...) or decrypt(...) with a different key type, compilation will succeed, but linking will fail with an error like:
undefined reference to `... encrypt<std::array<unsigned char,48>>(...)`
Supported Key Types
At the moment, the library guarantees instantiations for the following containers:
std::array<std::uint8_t, 16>(AES-128)std::array<std::uint8_t, 24>(AES-192)std::array<std::uint8_t, 32>(AES-256)std::vector<std::uint8_t>(length is validated at runtime as 16/24/32) These types are supported across all template utilities (encrypt,decrypt,encrypt_gcm,decrypt_gcm, and the corresponding convenience wrappers).
Utilities in aes_cpp::utils:
generate_iv_16()→ 16-byte IV for CBC/CFB/CTR (use CSPRNG or a counter scheme ensuring uniqueness per key)generate_iv_12()→ 12-byte IV for GCM (never reuse with the same key). Random 96-bit IVs are acceptable for many use-cases; a deterministic counter/nonce scheme eliminates collision risk within one key’s lifetime.- Use a CSPRNG for random IVs.
Core AES operates on 16-byte blocks and expects callers to supply padded data for ECB/CBC.
If the input size is not a multiple of 16, an exception is thrown. aes_cpp::utils provides
PKCS#7 helpers and higher-level CBC helpers that apply padding automatically.
Most APIs provide std::vector<uint8_t> overloads. They do not copy the input;
they allocate a new output vector and write results directly into it (return uses
NRVO/move).
For zero-allocation scenarios, use pointer/size APIs with a caller-provided output buffer,
or in-place pointer APIs (same buffer for input/output) where applicable.
Note: DecryptGCM normalizes the tag to 16 bytes (may copy/resize the tag).
A span-like API may be added later.
utils::constant_time_equal(a, b) compares byte sequences in a way that reduces timing leakage for equal-length inputs. Contract: treat lengths as public; compare lengths in protocol logic, then call the function only when sizes match.
Hardware path is compiled only when AES-NI/PCLMUL instructions are enabled at build time; runtime CPUID then selects between hardware and software paths.
- x86/x86_64: runtime AES-NI detection when compiled with AES-NI/PCLMUL support; hardware path when available, otherwise software fallback.
- GHASH (GCM) uses PCLMULQDQ with SSSE3 shuffles when available.
- Non-x86 (e.g., ARMv8): currently uses software path (no ARM Crypto Extensions yet).
- CMake option:
AES_CPP_ENABLE_AESNI(default ON) adds the necessary compiler flags on GCC/Clang x86 targets. - GCC/Clang: pass
-maes -mpclmulto compile AES-NI/PCLMUL code paths. - MSVC: AES-NI intrinsics enabled by default;
/arch:AVX2is optional for wider SIMD. CPUID selects the path at runtime. - Without these flags, the software path is always used.
Requirements: MSVC, CMake, vcpkg
git clone https://github.com/microsoft/vcpkg.git .\vcpkg\bootstrap-vcpkg.bat .\vcpkg\vcpkg install --x-feature=tests cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_MANIFEST_FEATURES=tests -DAES_CPP_BUILD_TESTS=ON cmake --build build --config Release ctest --test-dir build -C Release
Linking example (MSVC): link against build\Release\aes_cpp.lib or consume via find_package after cmake --install.
std::invalid_argument: null key/IV/tag/AAD; invalid IV size (GCM requires 12 bytes); tag size > 16.std::length_error: ECB/CBC input not multiple of 16; GCM AAD/length bounds; CTR counter overflow.std::runtime_error: GCM authentication failed (output buffer is zeroized before throwing).
AES methods are safe for concurrent use per instance (key cache is mutex-protected; per-call state is local).
-
Clone:
git clone https://github.com/NewYaroslav/aes-cpp.git -
Hooks:
./setup-hooks.shenables clang-format pre-commit -
Local GTest (optional):
./dev/install_gtest.sh -
Docker:
docker-compose up -d -
Make shortcuts:
make build_all— build all targetsmake build_test— build tests (-DAES_CPP_BUILD_TESTS=ON)make build_debug— builddebug(definesAESCPP_DEBUG)make build_profile— buildprofilemake build_speed_test— buildspeedtestmake build_release— build optimizedreleasemake test— run testsmake debug/make profile/make speed_test/make release— run respective binariesmake style_fix— fix code stylemake clean— cleanbin
Binaries in bin/:
test— runs testsdebug— debug build (main fromdev/main.cpp)profile— gprof build (main fromdev/main.cpp)speedtest— performance test (main fromspeedtest/main.cpp)release— optimized build (main fromdev/main.cpp)
Compilers/architectures? GCC, Clang, MSVC on x86/x86_64 are covered by CI. Other platforms should work with a compatible C++11 compiler; they currently use the software path.
No AES-NI? The library falls back to the portable software implementation; expect lower performance.
Submodule vs package? As a submodule use add_subdirectory. As an installed package use find_package(aes_cpp CONFIG REQUIRED) and link aes_cpp::aes_cpp.
Note: the aescpp alias is build-tree only; consumers should use aes_cpp::aes_cpp.
vcpkg triplets? Set explicitly when needed, e.g. vcpkg install --triplet x64-windows.
- hmac-cpp — HMAC for authentication
- siphash-hpp — header-only SipHash
- ADVobfuscator — compile-time obfuscation (C++20)
- obfy — generate license verification code
- AES (Wikipedia)
MIT — see LICENSE.