initial commit

This commit is contained in:
Brandon Lehmann
2026-02-24 18:11:26 -05:00
commit cc49624c7a
53 changed files with 5153 additions and 0 deletions

213
CMakeLists.txt Normal file
View File

@@ -0,0 +1,213 @@
cmake_minimum_required(VERSION 3.10)
project(tinyaes VERSION 0.1.0 LANGUAGES CXX)
include(CheckCXXCompilerFlag)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
option(BUILD_SHARED_LIBS "Build shared library" OFF)
option(BUILD_TESTS "Build unit tests" OFF)
option(BUILD_BENCHMARKS "Build benchmarks" OFF)
option(FORCE_PORTABLE "Disable SIMD backends; use only portable code" OFF)
# --- Library sources ---
set(TINYAES_SOURCES
src/secure_zero.cpp
src/cpuid.cpp
src/iv_generate.cpp
src/keyschedule.cpp
src/ecb.cpp
src/cbc.cpp
src/ctr.cpp
src/gcm.cpp
src/backend/aes_portable.cpp
src/backend/ghash_portable.cpp
)
# Conditionally add SIMD backend sources on x86_64
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64" AND NOT FORCE_PORTABLE)
list(APPEND TINYAES_SOURCES
src/backend/aes_aesni.cpp
src/backend/aes_vaes.cpp
src/backend/ghash_pclmulqdq.cpp
src/backend/ghash_vpclmulqdq.cpp
)
endif()
# Conditionally add ARM64 crypto extension backend sources
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64" AND NOT FORCE_PORTABLE)
list(APPEND TINYAES_SOURCES
src/backend/aes_arm_ce.cpp
src/backend/ghash_arm_ce.cpp
)
endif()
add_library(tinyaes ${TINYAES_SOURCES})
# --- Shared library symbol visibility ---
if(BUILD_SHARED_LIBS)
set_target_properties(tinyaes PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
)
target_compile_definitions(tinyaes PUBLIC TINYAES_SHARED=1)
target_compile_definitions(tinyaes PRIVATE TINYAES_BUILDING=1)
endif()
if(FORCE_PORTABLE)
target_compile_definitions(tinyaes PUBLIC TINYAES_FORCE_PORTABLE=1)
endif()
target_include_directories(tinyaes PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
# Internal headers (src/) for the library itself
target_include_directories(tinyaes PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
set_target_properties(tinyaes PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
)
# --- Link bcrypt on Windows for BCryptGenRandom ---
if(WIN32)
target_link_libraries(tinyaes PRIVATE bcrypt)
endif()
# --- Detect MinGW environment ---
# Only use CMake's built-in MINGW (set for GCC with MinGW). Clang on Windows can
# target either MinGW or MSVC ABI, and the linker varies (GNU ld vs lld-link) —
# applying the wrong linker flags breaks the build. We let Clang on Windows rely
# on the OS defaults for DEP/ASLR (enabled by default on modern Windows) rather
# than guessing the linker style.
# --- Hardening flags ---
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(tinyaes PRIVATE
-Wall -Wextra -Wpedantic -Werror
-Wconversion -Wsign-conversion -Wshadow
-Wcast-align -Wundef
-Wformat=2 -Wformat-security
-fno-strict-overflow
)
# Stack protector: requires libssp or compiler-rt at link time.
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_FLAGS "-fstack-protector-strong")
check_cxx_source_compiles("int main() { char buf[64]; buf[0] = 0; return buf[0]; }" HAS_STACK_PROTECTOR_STRONG)
unset(CMAKE_REQUIRED_FLAGS)
if(HAS_STACK_PROTECTOR_STRONG)
target_compile_options(tinyaes PRIVATE -fstack-protector-strong)
endif()
# -fPIC is implied on Windows PE; only needed on ELF/Mach-O
if(NOT WIN32)
target_compile_options(tinyaes PRIVATE -fPIC)
# Stack clash protection (Linux only)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_compile_options(tinyaes PRIVATE -fstack-clash-protection)
endif()
endif()
# Intel CET control flow integrity (x86_64 only)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
check_cxx_compiler_flag("-fcf-protection=full" HAS_CF_PROTECTION)
if(HAS_CF_PROTECTION)
target_compile_options(tinyaes PRIVATE -fcf-protection=full)
endif()
endif()
# -Wstrict-aliasing=2 only for GCC
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(tinyaes PRIVATE -Wstrict-aliasing=2)
target_compile_options(tinyaes PRIVATE
$<$<CONFIG:Debug>:-D_GLIBCXX_ASSERTIONS>
)
endif()
# _FORTIFY_SOURCE requires optimization; not reliably supported on MinGW
if(NOT WIN32)
target_compile_options(tinyaes PRIVATE
$<$<NOT:$<CONFIG:Debug>>:-D_FORTIFY_SOURCE=2>
)
endif()
# Linker hardening
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS
" -Wl,-z,relro,-z,now -Wl,-z,noexecstack")
if(BUILD_SHARED_LIBS)
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-z,defs")
endif()
endif()
# macOS: -bind_at_load is deprecated on modern macOS (eager binding is the default)
# MinGW linker hardening (DEP + ASLR) — only for GCC+MinGW (known GNU ld)
if(MINGW)
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS
" -Wl,--nxcompat -Wl,--dynamicbase -Wl,--high-entropy-va")
endif()
elseif(MSVC)
target_compile_options(tinyaes PRIVATE
/W4 /WX
/sdl
/GS
/guard:cf
)
check_cxx_compiler_flag("/Qspectre" HAS_QSPECTRE)
if(HAS_QSPECTRE)
target_compile_options(tinyaes PRIVATE /Qspectre)
endif()
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS
" /DYNAMICBASE /NXCOMPAT /HIGHENTROPYVA")
check_cxx_compiler_flag("/guard:ehcont" HAS_GUARD_EHCONT)
if(HAS_GUARD_EHCONT)
target_compile_options(tinyaes PRIVATE /guard:ehcont)
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS " /guard:ehcont /CETCOMPAT")
endif()
endif()
# --- Per-backend SIMD compile flags ---
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64" AND NOT FORCE_PORTABLE)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
set_source_files_properties(src/backend/aes_aesni.cpp PROPERTIES
COMPILE_FLAGS "-maes -msse4.1")
set_source_files_properties(src/backend/aes_vaes.cpp PROPERTIES
COMPILE_FLAGS "-mavx512f -mvaes -maes -msse4.1")
set_source_files_properties(src/backend/ghash_pclmulqdq.cpp PROPERTIES
COMPILE_FLAGS "-mpclmul -msse4.1")
set_source_files_properties(src/backend/ghash_vpclmulqdq.cpp PROPERTIES
COMPILE_FLAGS "-mavx512f -mvpclmulqdq -mpclmul -msse4.1")
elseif(MSVC)
# MSVC: AES-NI and PCLMULQDQ available without extra flags on x64
set_source_files_properties(src/backend/aes_vaes.cpp PROPERTIES
COMPILE_FLAGS "/arch:AVX512")
set_source_files_properties(src/backend/ghash_vpclmulqdq.cpp PROPERTIES
COMPILE_FLAGS "/arch:AVX512")
endif()
endif()
# --- Per-backend ARM64 crypto extension compile flags ---
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64" AND NOT FORCE_PORTABLE)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
set_source_files_properties(src/backend/aes_arm_ce.cpp PROPERTIES
COMPILE_FLAGS "-march=armv8-a+crypto")
set_source_files_properties(src/backend/ghash_arm_ce.cpp PROPERTIES
COMPILE_FLAGS "-march=armv8-a+crypto")
endif()
# MSVC on ARM64: crypto extensions are enabled by default
endif()
# --- Tests ---
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
# --- Benchmarks ---
if(BUILD_BENCHMARKS)
add_subdirectory(bench)
endif()
# --- Fuzzing (Clang only, Linux only) ---
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_subdirectory(fuzz)
endif()

25
LICENSE Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2025-2026, Brandon Lehmann
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

20
bench/CMakeLists.txt Normal file
View File

@@ -0,0 +1,20 @@
add_executable(tinyaes_benchmarks
bench_all.cpp
)
target_link_libraries(tinyaes_benchmarks PRIVATE tinyaes)
target_include_directories(tinyaes_benchmarks PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../src
)
set_target_properties(tinyaes_benchmarks PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(tinyaes_benchmarks PRIVATE -Wall -Wextra -Wpedantic)
elseif(MSVC)
target_compile_options(tinyaes_benchmarks PRIVATE /W4)
endif()

85
bench/bench_all.cpp Normal file
View File

@@ -0,0 +1,85 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "tinyaes.h"
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <vector>
static constexpr size_t BENCH_SIZE = 1024 * 1024; // 1 MiB
static constexpr int ITERATIONS = 100;
template<typename Fn> static double bench(const char *name, Fn &&fn)
{
// Warmup
fn();
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERATIONS; ++i)
{
fn();
}
auto end = std::chrono::high_resolution_clock::now();
double ms = std::chrono::duration<double, std::milli>(end - start).count();
double total_bytes = static_cast<double>(BENCH_SIZE) * ITERATIONS;
double mib_per_sec = (total_bytes / (1024.0 * 1024.0)) / (ms / 1000.0);
std::printf("%-30s %8.2f MiB/s (%6.2f ms total)\n", name, mib_per_sec, ms);
return mib_per_sec;
}
int main()
{
std::vector<uint8_t> key_128(16, 0x42);
std::vector<uint8_t> key_256(32, 0x42);
std::vector<uint8_t> iv_16(16, 0x01);
std::vector<uint8_t> iv_12(12, 0x01);
std::vector<uint8_t> plaintext(BENCH_SIZE, 0x55);
std::vector<uint8_t> aad = {0xAA, 0xBB, 0xCC, 0xDD};
std::printf("TinyAES Benchmarks (%zu bytes x %d iterations)\n", BENCH_SIZE, ITERATIONS);
std::printf("================================================================\n");
// ECB
bench("ECB-128 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::ecb_encrypt(key_128, plaintext, ct);
});
bench("ECB-256 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::ecb_encrypt(key_256, plaintext, ct);
});
// CBC
bench("CBC-128 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::cbc_encrypt(key_128, iv_16, plaintext, ct);
});
bench("CBC-256 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::cbc_encrypt(key_256, iv_16, plaintext, ct);
});
// CTR
bench("CTR-128 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::ctr_crypt(key_128, iv_16, plaintext, ct);
});
bench("CTR-256 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::ctr_crypt(key_256, iv_16, plaintext, ct);
});
// GCM
bench("GCM-128 encrypt", [&]() {
std::vector<uint8_t> ct, tag;
tinyaes::gcm_encrypt(key_128, iv_12, aad, plaintext, ct, tag);
});
bench("GCM-256 encrypt", [&]() {
std::vector<uint8_t> ct, tag;
tinyaes::gcm_encrypt(key_256, iv_12, aad, plaintext, ct, tag);
});
return 0;
}

24
fuzz/CMakeLists.txt Normal file
View File

@@ -0,0 +1,24 @@
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
return()
endif()
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
return()
endif()
set(FUZZ_TARGETS fuzz_ecb fuzz_cbc fuzz_ctr fuzz_gcm)
foreach(target ${FUZZ_TARGETS})
add_executable(${target} ${target}.cpp)
target_link_libraries(${target} PRIVATE tinyaes)
target_include_directories(${target} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../src
)
set_target_properties(${target} PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/fuzz
)
target_compile_options(${target} PRIVATE -fsanitize=fuzzer,address)
set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " -fsanitize=fuzzer,address")
endforeach()

35
fuzz/fuzz_cbc.cpp Normal file
View File

@@ -0,0 +1,35 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "tinyaes/cbc.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
if (size < 32)
return 0;
// First 16 bytes = key, next 16 bytes = IV, rest = plaintext
std::vector<uint8_t> key(data, data + 16);
std::vector<uint8_t> iv(data + 16, data + 32);
size_t remaining = size - 32;
if (remaining == 0)
return 0;
// Use PKCS#7 which accepts any length
std::vector<uint8_t> plaintext(data + 32, data + size);
std::vector<uint8_t> ct, pt;
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
if (result != tinyaes::Result::Success)
return 0;
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
assert(result == tinyaes::Result::Success);
assert(pt == plaintext);
return 0;
}

29
fuzz/fuzz_ctr.cpp Normal file
View File

@@ -0,0 +1,29 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "tinyaes/ctr.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
if (size < 33)
return 0;
// First 16 bytes = key, next 16 bytes = IV, rest = plaintext
std::vector<uint8_t> key(data, data + 16);
std::vector<uint8_t> iv(data + 16, data + 32);
std::vector<uint8_t> plaintext(data + 32, data + size);
std::vector<uint8_t> ct, pt;
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
if (result != tinyaes::Result::Success)
return 0;
result = tinyaes::ctr_crypt(key, iv, ct, pt);
assert(result == tinyaes::Result::Success);
assert(pt == plaintext);
return 0;
}

32
fuzz/fuzz_ecb.cpp Normal file
View File

@@ -0,0 +1,32 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "tinyaes/ecb.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
if (size < 16)
return 0;
// Use first 16 bytes as key, rest as plaintext (block-aligned)
std::vector<uint8_t> key(data, data + 16);
size_t pt_len = ((size - 16) / 16) * 16;
if (pt_len == 0)
return 0;
std::vector<uint8_t> plaintext(data + 16, data + 16 + pt_len);
std::vector<uint8_t> ct, pt;
auto result = tinyaes::ecb_encrypt(key, plaintext, ct);
if (result != tinyaes::Result::Success)
return 0;
result = tinyaes::ecb_decrypt(key, ct, pt);
assert(result == tinyaes::Result::Success);
assert(pt == plaintext);
return 0;
}

35
fuzz/fuzz_gcm.cpp Normal file
View File

@@ -0,0 +1,35 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "tinyaes/gcm.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
if (size < 29)
return 0;
// First 16 bytes = key, next 12 bytes = IV, 1 byte = AAD length, rest split
std::vector<uint8_t> key(data, data + 16);
std::vector<uint8_t> iv(data + 16, data + 28);
size_t aad_len = data[28] % (size - 29 + 1);
if (29 + aad_len > size)
aad_len = 0;
std::vector<uint8_t> aad(data + 29, data + 29 + aad_len);
std::vector<uint8_t> plaintext(data + 29 + aad_len, data + size);
std::vector<uint8_t> ct, tag, pt;
auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag);
if (result != tinyaes::Result::Success)
return 0;
result = tinyaes::gcm_decrypt(key, iv, aad, ct, tag, pt);
assert(result == tinyaes::Result::Success);
assert(pt == plaintext);
return 0;
}

34
include/tinyaes.h Normal file
View File

@@ -0,0 +1,34 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include "tinyaes/version.h"
#include "tinyaes/common.h"
#include "tinyaes/ecb.h"
#include "tinyaes/cbc.h"
#include "tinyaes/ctr.h"
#include "tinyaes/gcm.h"

111
include/tinyaes/cbc.h Normal file
View File

@@ -0,0 +1,111 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include "tinyaes/common.h"
#ifdef __cplusplus
extern "C"
{
#endif
// CBC without padding — input must be multiple of 16 bytes
TINYAES_EXPORT int tinyaes_cbc_encrypt(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
size_t ciphertext_len);
TINYAES_EXPORT int tinyaes_cbc_decrypt(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t *plaintext,
size_t plaintext_len);
// CBC with PKCS#7 padding
TINYAES_EXPORT int tinyaes_cbc_encrypt_pkcs7(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
size_t *ciphertext_len);
TINYAES_EXPORT int tinyaes_cbc_decrypt_pkcs7(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t *plaintext,
size_t *plaintext_len);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
#include <vector>
namespace tinyaes
{
Result cbc_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext);
Result cbc_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &plaintext);
Result cbc_encrypt_pkcs7(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext);
Result cbc_decrypt_pkcs7(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &plaintext);
} // namespace tinyaes
#endif

107
include/tinyaes/common.h Normal file
View File

@@ -0,0 +1,107 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstddef>
#include <cstdint>
#include <vector>
// Symbol visibility for shared library builds
#if defined(TINYAES_SHARED)
#if defined(_WIN32) || defined(__CYGWIN__)
#if defined(TINYAES_BUILDING)
#define TINYAES_EXPORT __declspec(dllexport)
#else
#define TINYAES_EXPORT __declspec(dllimport)
#endif
#elif defined(__GNUC__) || defined(__clang__)
#define TINYAES_EXPORT __attribute__((visibility("default")))
#else
#define TINYAES_EXPORT
#endif
#else
#define TINYAES_EXPORT
#endif
// AES key sizes
#define TINYAES_KEY_128 16
#define TINYAES_KEY_192 24
#define TINYAES_KEY_256 32
#define TINYAES_BLOCK_SIZE 16
#define TINYAES_GCM_TAG_SIZE 16
#define TINYAES_GCM_IV_SIZE 12
// C error codes
#define TINYAES_SUCCESS 0
#define TINYAES_ERROR_INVALID_KEY_SIZE (-1)
#define TINYAES_ERROR_INVALID_INPUT (-2)
#define TINYAES_ERROR_INVALID_PADDING (-3)
#define TINYAES_ERROR_AUTH_FAILED (-4)
#define TINYAES_ERROR_BUFFER_TOO_SMALL (-5)
#ifdef __cplusplus
extern "C"
{
#endif
TINYAES_EXPORT int tinyaes_constant_time_equal(const uint8_t *a, const uint8_t *b, size_t len);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
namespace tinyaes
{
enum class Result
{
Success = 0,
InvalidKeySize = -1,
InvalidInput = -2,
InvalidPadding = -3,
AuthFailed = -4,
BufferTooSmall = -5
};
void secure_zero(void *ptr, size_t len);
bool constant_time_equal(const uint8_t *a, const uint8_t *b, size_t len);
inline bool constant_time_equal(const std::vector<uint8_t> &a, const std::vector<uint8_t> &b)
{
if (a.size() != b.size())
return false;
return constant_time_equal(a.data(), b.data(), a.size());
}
int generate_iv(uint8_t *out, size_t len);
} // namespace tinyaes
#endif

66
include/tinyaes/ctr.h Normal file
View File

@@ -0,0 +1,66 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include "tinyaes/common.h"
#ifdef __cplusplus
extern "C"
{
#endif
// CTR encrypt/decrypt (symmetric operation)
TINYAES_EXPORT int tinyaes_ctr_crypt(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *input,
size_t input_len,
uint8_t *output,
size_t output_len);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
#include <vector>
namespace tinyaes
{
// CTR mode: encrypt and decrypt are the same operation
Result ctr_crypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &input,
std::vector<uint8_t> &output);
} // namespace tinyaes
#endif

75
include/tinyaes/ecb.h Normal file
View File

@@ -0,0 +1,75 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include "tinyaes/common.h"
#ifdef __cplusplus
extern "C"
{
#endif
TINYAES_EXPORT int tinyaes_ecb_encrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
size_t ciphertext_len);
TINYAES_EXPORT int tinyaes_ecb_decrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t *plaintext,
size_t plaintext_len);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
#include <vector>
namespace tinyaes
{
Result ecb_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext);
Result ecb_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &plaintext);
} // namespace tinyaes
#endif

91
include/tinyaes/gcm.h Normal file
View File

@@ -0,0 +1,91 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include "tinyaes/common.h"
#ifdef __cplusplus
extern "C"
{
#endif
TINYAES_EXPORT int tinyaes_gcm_encrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *iv,
size_t iv_len,
const uint8_t *aad,
size_t aad_len,
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t tag[16]);
TINYAES_EXPORT int tinyaes_gcm_decrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *iv,
size_t iv_len,
const uint8_t *aad,
size_t aad_len,
const uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t *plaintext,
size_t plaintext_len,
const uint8_t tag[16]);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
#include <vector>
namespace tinyaes
{
Result gcm_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &aad,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &tag);
Result gcm_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &aad,
const std::vector<uint8_t> &ciphertext,
const std::vector<uint8_t> &tag,
std::vector<uint8_t> &plaintext);
} // namespace tinyaes
#endif

32
include/tinyaes/version.h Normal file
View File

@@ -0,0 +1,32 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#define TINYAES_VERSION_MAJOR 0
#define TINYAES_VERSION_MINOR 1
#define TINYAES_VERSION_PATCH 0
#define TINYAES_VERSION_STRING "0.1.0"

229
src/backend/aes_aesni.cpp Normal file
View File

@@ -0,0 +1,229 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// AES-NI (x86_64) backend: single-block encrypt/decrypt + pipelined CTR
#if defined(__x86_64__) || defined(_M_X64)
#include "internal/aes_impl.h"
#include "internal/endian.h"
#include <cstring>
#include <wmmintrin.h> // AES-NI intrinsics
#include <smmintrin.h> // SSE4.1
namespace tinyaes
{
namespace internal
{
// Helper: AES-128 key expansion assist
static inline __m128i aes_128_key_assist(__m128i key, __m128i gen)
{
gen = _mm_shuffle_epi32(gen, 0xFF);
key = _mm_xor_si128(key, _mm_slli_si128(key, 4));
key = _mm_xor_si128(key, _mm_slli_si128(key, 4));
key = _mm_xor_si128(key, _mm_slli_si128(key, 4));
return _mm_xor_si128(key, gen);
}
// AES-NI key expansion
void aes_key_expand_aesni(const uint8_t *key, size_t key_len, uint32_t *rk)
{
__m128i *rk128 = reinterpret_cast<__m128i *>(rk);
if (key_len == 16)
{
// AES-128: 11 round keys
__m128i k = _mm_loadu_si128(reinterpret_cast<const __m128i *>(key));
rk128[0] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x01));
rk128[1] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x02));
rk128[2] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x04));
rk128[3] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x08));
rk128[4] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x10));
rk128[5] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x20));
rk128[6] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x40));
rk128[7] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x80));
rk128[8] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x1B));
rk128[9] = k;
k = aes_128_key_assist(k, _mm_aeskeygenassist_si128(k, 0x36));
rk128[10] = k;
}
else
{
// For AES-192 and AES-256, use portable key expansion then convert
// from big-endian uint32_t words to native byte order so that
// _mm_loadu_si128 loads the correct key bytes for AES-NI.
aes_key_expand_portable(key, key_len, rk);
int nr = static_cast<int>(key_len / 4) + 6;
int total_words = 4 * (nr + 1);
for (int i = 0; i < total_words; ++i)
{
uint32_t w = rk[i];
rk[i] = ((w >> 24) & 0xFF) | ((w >> 8) & 0xFF00) | ((w << 8) & 0xFF0000)
| ((w << 24) & 0xFF000000u);
}
}
}
void aes_encrypt_block_aesni(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16])
{
const __m128i *rk128 = reinterpret_cast<const __m128i *>(rk);
__m128i block = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in));
block = _mm_xor_si128(block, rk128[0]);
for (int i = 1; i < rounds; ++i)
{
block = _mm_aesenc_si128(block, rk128[i]);
}
block = _mm_aesenclast_si128(block, rk128[rounds]);
_mm_storeu_si128(reinterpret_cast<__m128i *>(out), block);
}
void aes_decrypt_block_aesni(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16])
{
const __m128i *rk128 = reinterpret_cast<const __m128i *>(rk);
// Need inverse round keys for AES-NI decrypt
// InvMixColumns on round keys 1..rounds-1
__m128i inv_rk[15];
inv_rk[0] = rk128[rounds];
for (int i = 1; i < rounds; ++i)
{
inv_rk[i] = _mm_aesimc_si128(rk128[rounds - i]);
}
inv_rk[rounds] = rk128[0];
__m128i block = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in));
block = _mm_xor_si128(block, inv_rk[0]);
for (int i = 1; i < rounds; ++i)
{
block = _mm_aesdec_si128(block, inv_rk[i]);
}
block = _mm_aesdeclast_si128(block, inv_rk[rounds]);
_mm_storeu_si128(reinterpret_cast<__m128i *>(out), block);
}
// Pipelined CTR: process 4 blocks at a time
void aes_ctr_pipeline_aesni(const uint32_t *rk, int rounds, const uint8_t *in, uint8_t *out, size_t blocks,
uint8_t ctr[16])
{
const __m128i *rk128 = reinterpret_cast<const __m128i *>(rk);
const __m128i one = _mm_set_epi32(0, 0, 0, 1);
// AES counter is big-endian in last 4 bytes, but _mm_loadu loads LE
// We need byte-swap for the counter increment
const __m128i bswap_mask =
_mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
__m128i ctr_block = _mm_loadu_si128(reinterpret_cast<const __m128i *>(ctr));
// Process 4 blocks at a time
size_t i = 0;
for (; i + 4 <= blocks; i += 4)
{
__m128i c0 = ctr_block;
__m128i c0_le = _mm_shuffle_epi8(c0, bswap_mask);
__m128i c1_le = _mm_add_epi64(c0_le, one);
__m128i c2_le = _mm_add_epi64(c1_le, one);
__m128i c3_le = _mm_add_epi64(c2_le, one);
__m128i c1 = _mm_shuffle_epi8(c1_le, bswap_mask);
__m128i c2 = _mm_shuffle_epi8(c2_le, bswap_mask);
__m128i c3 = _mm_shuffle_epi8(c3_le, bswap_mask);
// Initial round key XOR
__m128i b0 = _mm_xor_si128(c0, rk128[0]);
__m128i b1 = _mm_xor_si128(c1, rk128[0]);
__m128i b2 = _mm_xor_si128(c2, rk128[0]);
__m128i b3 = _mm_xor_si128(c3, rk128[0]);
// Middle rounds
for (int r = 1; r < rounds; ++r)
{
b0 = _mm_aesenc_si128(b0, rk128[r]);
b1 = _mm_aesenc_si128(b1, rk128[r]);
b2 = _mm_aesenc_si128(b2, rk128[r]);
b3 = _mm_aesenc_si128(b3, rk128[r]);
}
// Final round
b0 = _mm_aesenclast_si128(b0, rk128[rounds]);
b1 = _mm_aesenclast_si128(b1, rk128[rounds]);
b2 = _mm_aesenclast_si128(b2, rk128[rounds]);
b3 = _mm_aesenclast_si128(b3, rk128[rounds]);
// XOR with plaintext
__m128i p0 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in + i * 16));
__m128i p1 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in + i * 16 + 16));
__m128i p2 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in + i * 16 + 32));
__m128i p3 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in + i * 16 + 48));
_mm_storeu_si128(reinterpret_cast<__m128i *>(out + i * 16), _mm_xor_si128(b0, p0));
_mm_storeu_si128(reinterpret_cast<__m128i *>(out + i * 16 + 16), _mm_xor_si128(b1, p1));
_mm_storeu_si128(reinterpret_cast<__m128i *>(out + i * 16 + 32), _mm_xor_si128(b2, p2));
_mm_storeu_si128(reinterpret_cast<__m128i *>(out + i * 16 + 48), _mm_xor_si128(b3, p3));
// Advance counter by 4
__m128i next_le = _mm_add_epi64(c3_le, one);
ctr_block = _mm_shuffle_epi8(next_le, bswap_mask);
}
// Process remaining blocks one at a time
for (; i < blocks; ++i)
{
__m128i b = _mm_xor_si128(ctr_block, rk128[0]);
for (int r = 1; r < rounds; ++r)
{
b = _mm_aesenc_si128(b, rk128[r]);
}
b = _mm_aesenclast_si128(b, rk128[rounds]);
__m128i p = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in + i * 16));
_mm_storeu_si128(reinterpret_cast<__m128i *>(out + i * 16), _mm_xor_si128(b, p));
__m128i ctr_le = _mm_shuffle_epi8(ctr_block, bswap_mask);
ctr_le = _mm_add_epi64(ctr_le, one);
ctr_block = _mm_shuffle_epi8(ctr_le, bswap_mask);
}
// Store updated counter
_mm_storeu_si128(reinterpret_cast<__m128i *>(ctr), ctr_block);
}
} // namespace internal
} // namespace tinyaes
#endif // x86_64

127
src/backend/aes_arm_ce.cpp Normal file
View File

@@ -0,0 +1,127 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// ARM Crypto Extensions AES backend
#if defined(__aarch64__) || defined(_M_ARM64)
#include "internal/aes_impl.h"
#include "internal/endian.h"
#include <cstring>
#if defined(_MSC_VER)
#include <arm64_neon.h>
#else
#include <arm_neon.h>
#endif
namespace tinyaes
{
namespace internal
{
// ARM AES: vaeseq + vaesmcq for encryption, vaesdq + vaesimcq for decryption
// Note: ARM AES instructions combine SubBytes+ShiftRows (vaese) and MixColumns (vaesmc)
// The key XOR is done separately (veorq).
void aes_encrypt_block_arm_ce(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16])
{
const uint8_t *rk8 = reinterpret_cast<const uint8_t *>(rk);
uint8x16_t block = vld1q_u8(in);
// ARM vaese does: AddRoundKey ^ SubBytes ^ ShiftRows
// So we need: vaese(block, key[i]) then vaesmcq for MixColumns
for (int i = 0; i < rounds - 1; ++i)
{
uint8x16_t key = vld1q_u8(rk8 + i * 16);
block = vaeseq_u8(block, key);
block = vaesmcq_u8(block);
}
// Last round: no MixColumns
uint8x16_t key_last = vld1q_u8(rk8 + (rounds - 1) * 16);
block = vaeseq_u8(block, key_last);
// Final AddRoundKey
uint8x16_t key_final = vld1q_u8(rk8 + rounds * 16);
block = veorq_u8(block, key_final);
vst1q_u8(out, block);
}
void aes_decrypt_block_arm_ce(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16])
{
const uint8_t *rk8 = reinterpret_cast<const uint8_t *>(rk);
uint8x16_t block = vld1q_u8(in);
// Decryption uses inverse round keys in reverse order
for (int i = rounds; i > 1; --i)
{
uint8x16_t key = vld1q_u8(rk8 + i * 16);
block = vaesdq_u8(block, key);
block = vaesimcq_u8(block);
}
// Last round: no InvMixColumns
uint8x16_t key_1 = vld1q_u8(rk8 + 16);
block = vaesdq_u8(block, key_1);
// Final AddRoundKey
uint8x16_t key_0 = vld1q_u8(rk8);
block = veorq_u8(block, key_0);
vst1q_u8(out, block);
}
void aes_ctr_pipeline_arm_ce(const uint32_t *rk, int rounds, const uint8_t *in, uint8_t *out, size_t blocks,
uint8_t ctr[16])
{
const uint8_t *rk8 = reinterpret_cast<const uint8_t *>(rk);
for (size_t i = 0; i < blocks; ++i)
{
uint8x16_t block = vld1q_u8(ctr);
for (int r = 0; r < rounds - 1; ++r)
{
uint8x16_t key = vld1q_u8(rk8 + r * 16);
block = vaeseq_u8(block, key);
block = vaesmcq_u8(block);
}
uint8x16_t key_last = vld1q_u8(rk8 + (rounds - 1) * 16);
block = vaeseq_u8(block, key_last);
uint8x16_t key_final = vld1q_u8(rk8 + rounds * 16);
block = veorq_u8(block, key_final);
// XOR with plaintext
uint8x16_t pt = vld1q_u8(in + i * 16);
vst1q_u8(out + i * 16, veorq_u8(block, pt));
increment_be32(ctr);
}
}
} // namespace internal
} // namespace tinyaes
#endif // aarch64

View File

@@ -0,0 +1,338 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Portable AES implementation using T-tables (FIPS 197).
// NOTE: T-table implementations are susceptible to cache-timing side channels.
// For production use on x86, the AES-NI backend is preferred.
#include "internal/aes_impl.h"
#include "internal/endian.h"
#include <cstring>
namespace tinyaes
{
namespace internal
{
// AES S-box (SubBytes)
// clang-format off
static const uint8_t sbox[256] = {
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
};
// Inverse S-box (InvSubBytes)
static const uint8_t inv_sbox[256] = {
0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d
};
// clang-format on
// GF(2^8) multiply by 2
static inline uint8_t xtime(uint8_t x)
{
return static_cast<uint8_t>((x << 1) ^ (((x >> 7) & 1) * 0x1b));
}
// GF(2^8) multiply
static inline uint8_t gmul(uint8_t a, uint8_t b)
{
uint8_t p = 0;
for (int i = 0; i < 8; ++i)
{
if (b & 1)
p ^= a;
uint8_t hi = a & 0x80;
a = static_cast<uint8_t>(a << 1);
if (hi)
a ^= 0x1b;
b >>= 1;
}
return p;
}
// Encryption T-tables (Te0..Te3)
// Te0[x] = S[x].[02, 01, 01, 03] (column of MixColumns * SubBytes)
static uint32_t Te0[256], Te1[256], Te2[256], Te3[256];
static uint32_t Td0[256], Td1[256], Td2[256], Td3[256];
static bool tables_initialized = false;
static void init_tables()
{
if (tables_initialized)
return;
for (int i = 0; i < 256; ++i)
{
uint8_t s = sbox[i];
uint8_t s2 = xtime(s);
uint8_t s3 = static_cast<uint8_t>(s2 ^ s);
// Te0[i] = [s2, s, s, s3] as big-endian 32-bit word
Te0[i] = (static_cast<uint32_t>(s2) << 24) | (static_cast<uint32_t>(s) << 16)
| (static_cast<uint32_t>(s) << 8) | static_cast<uint32_t>(s3);
Te1[i] = (Te0[i] >> 8) | (Te0[i] << 24);
Te2[i] = (Te0[i] >> 16) | (Te0[i] << 16);
Te3[i] = (Te0[i] >> 24) | (Te0[i] << 8);
// Decryption tables
uint8_t si = inv_sbox[i];
uint8_t si9 = gmul(si, 0x09);
uint8_t sib = gmul(si, 0x0b);
uint8_t sid = gmul(si, 0x0d);
uint8_t sie = gmul(si, 0x0e);
Td0[i] = (static_cast<uint32_t>(sie) << 24) | (static_cast<uint32_t>(si9) << 16)
| (static_cast<uint32_t>(sid) << 8) | static_cast<uint32_t>(sib);
Td1[i] = (Td0[i] >> 8) | (Td0[i] << 24);
Td2[i] = (Td0[i] >> 16) | (Td0[i] << 16);
Td3[i] = (Td0[i] >> 24) | (Td0[i] << 8);
}
tables_initialized = true;
}
// Round constants
static const uint8_t rcon[11] = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
// Key expansion (FIPS 197 Section 5.2)
void aes_key_expand_portable(const uint8_t *key, size_t key_len, uint32_t *rk)
{
init_tables();
int nk = static_cast<int>(key_len / 4); // 4, 6, or 8
int nr = nk + 6; // 10, 12, or 14
int total = 4 * (nr + 1); // total words
// Copy key into first Nk words
for (int i = 0; i < nk; ++i)
{
rk[i] = load_be32(key + 4 * i);
}
for (int i = nk; i < total; ++i)
{
uint32_t temp = rk[i - 1];
if (i % nk == 0)
{
// RotWord + SubWord + Rcon
temp = (static_cast<uint32_t>(sbox[(temp >> 16) & 0xff]) << 24)
| (static_cast<uint32_t>(sbox[(temp >> 8) & 0xff]) << 16)
| (static_cast<uint32_t>(sbox[temp & 0xff]) << 8)
| (static_cast<uint32_t>(sbox[(temp >> 24) & 0xff]));
temp ^= static_cast<uint32_t>(rcon[i / nk]) << 24;
}
else if (nk > 6 && (i % nk == 4))
{
// SubWord only for AES-256
temp = (static_cast<uint32_t>(sbox[(temp >> 24) & 0xff]) << 24)
| (static_cast<uint32_t>(sbox[(temp >> 16) & 0xff]) << 16)
| (static_cast<uint32_t>(sbox[(temp >> 8) & 0xff]) << 8)
| (static_cast<uint32_t>(sbox[temp & 0xff]));
}
rk[i] = rk[i - nk] ^ temp;
}
}
void aes_encrypt_block_portable(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16])
{
init_tables();
uint32_t s0 = load_be32(in) ^ rk[0];
uint32_t s1 = load_be32(in + 4) ^ rk[1];
uint32_t s2 = load_be32(in + 8) ^ rk[2];
uint32_t s3 = load_be32(in + 12) ^ rk[3];
uint32_t t0 = s0, t1 = s1, t2 = s2, t3 = s3;
int r = 1;
for (; r < rounds; ++r)
{
t0 = Te0[(s0 >> 24) & 0xff] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff]
^ rk[4 * r];
t1 = Te0[(s1 >> 24) & 0xff] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff]
^ rk[4 * r + 1];
t2 = Te0[(s2 >> 24) & 0xff] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff]
^ rk[4 * r + 2];
t3 = Te0[(s3 >> 24) & 0xff] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff]
^ rk[4 * r + 3];
s0 = t0;
s1 = t1;
s2 = t2;
s3 = t3;
}
// Final round (no MixColumns)
s0 = (static_cast<uint32_t>(sbox[(t0 >> 24) & 0xff]) << 24)
| (static_cast<uint32_t>(sbox[(t1 >> 16) & 0xff]) << 16)
| (static_cast<uint32_t>(sbox[(t2 >> 8) & 0xff]) << 8)
| (static_cast<uint32_t>(sbox[t3 & 0xff]));
s0 ^= rk[4 * rounds];
s1 = (static_cast<uint32_t>(sbox[(t1 >> 24) & 0xff]) << 24)
| (static_cast<uint32_t>(sbox[(t2 >> 16) & 0xff]) << 16)
| (static_cast<uint32_t>(sbox[(t3 >> 8) & 0xff]) << 8)
| (static_cast<uint32_t>(sbox[t0 & 0xff]));
s1 ^= rk[4 * rounds + 1];
s2 = (static_cast<uint32_t>(sbox[(t2 >> 24) & 0xff]) << 24)
| (static_cast<uint32_t>(sbox[(t3 >> 16) & 0xff]) << 16)
| (static_cast<uint32_t>(sbox[(t0 >> 8) & 0xff]) << 8)
| (static_cast<uint32_t>(sbox[t1 & 0xff]));
s2 ^= rk[4 * rounds + 2];
s3 = (static_cast<uint32_t>(sbox[(t3 >> 24) & 0xff]) << 24)
| (static_cast<uint32_t>(sbox[(t0 >> 16) & 0xff]) << 16)
| (static_cast<uint32_t>(sbox[(t1 >> 8) & 0xff]) << 8)
| (static_cast<uint32_t>(sbox[t2 & 0xff]));
s3 ^= rk[4 * rounds + 3];
store_be32(out, s0);
store_be32(out + 4, s1);
store_be32(out + 8, s2);
store_be32(out + 12, s3);
}
// Apply InvMixColumns to a single round-key word using Td tables + sbox.
// Td0[sbox[b]] = InvMixColumns column for byte b (since inv_sbox[sbox[b]] = b).
static inline uint32_t inv_mix_column(uint32_t w)
{
return Td0[sbox[(w >> 24) & 0xff]] ^ Td1[sbox[(w >> 16) & 0xff]] ^ Td2[sbox[(w >> 8) & 0xff]]
^ Td3[sbox[w & 0xff]];
}
void aes_decrypt_block_portable(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16])
{
init_tables();
uint32_t s0 = load_be32(in) ^ rk[4 * rounds];
uint32_t s1 = load_be32(in + 4) ^ rk[4 * rounds + 1];
uint32_t s2 = load_be32(in + 8) ^ rk[4 * rounds + 2];
uint32_t s3 = load_be32(in + 12) ^ rk[4 * rounds + 3];
uint32_t t0 = s0, t1 = s1, t2 = s2, t3 = s3;
int r = rounds - 1;
for (; r > 0; --r)
{
// Equivalent Inverse Cipher (FIPS 197 §5.3.5):
// Td tables combine InvSubBytes + InvMixColumns, so the round keys
// for middle rounds must be preprocessed with InvMixColumns.
uint32_t dk0 = inv_mix_column(rk[4 * r]);
uint32_t dk1 = inv_mix_column(rk[4 * r + 1]);
uint32_t dk2 = inv_mix_column(rk[4 * r + 2]);
uint32_t dk3 = inv_mix_column(rk[4 * r + 3]);
t0 = Td0[(s0 >> 24) & 0xff] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff]
^ dk0;
t1 = Td0[(s1 >> 24) & 0xff] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff]
^ dk1;
t2 = Td0[(s2 >> 24) & 0xff] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff]
^ dk2;
t3 = Td0[(s3 >> 24) & 0xff] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff]
^ dk3;
s0 = t0;
s1 = t1;
s2 = t2;
s3 = t3;
}
// Final round (no InvMixColumns)
s0 = (static_cast<uint32_t>(inv_sbox[(t0 >> 24) & 0xff]) << 24)
| (static_cast<uint32_t>(inv_sbox[(t3 >> 16) & 0xff]) << 16)
| (static_cast<uint32_t>(inv_sbox[(t2 >> 8) & 0xff]) << 8)
| (static_cast<uint32_t>(inv_sbox[t1 & 0xff]));
s0 ^= rk[0];
s1 = (static_cast<uint32_t>(inv_sbox[(t1 >> 24) & 0xff]) << 24)
| (static_cast<uint32_t>(inv_sbox[(t0 >> 16) & 0xff]) << 16)
| (static_cast<uint32_t>(inv_sbox[(t3 >> 8) & 0xff]) << 8)
| (static_cast<uint32_t>(inv_sbox[t2 & 0xff]));
s1 ^= rk[1];
s2 = (static_cast<uint32_t>(inv_sbox[(t2 >> 24) & 0xff]) << 24)
| (static_cast<uint32_t>(inv_sbox[(t1 >> 16) & 0xff]) << 16)
| (static_cast<uint32_t>(inv_sbox[(t0 >> 8) & 0xff]) << 8)
| (static_cast<uint32_t>(inv_sbox[t3 & 0xff]));
s2 ^= rk[2];
s3 = (static_cast<uint32_t>(inv_sbox[(t3 >> 24) & 0xff]) << 24)
| (static_cast<uint32_t>(inv_sbox[(t2 >> 16) & 0xff]) << 16)
| (static_cast<uint32_t>(inv_sbox[(t1 >> 8) & 0xff]) << 8)
| (static_cast<uint32_t>(inv_sbox[t0 & 0xff]));
s3 ^= rk[3];
store_be32(out, s0);
store_be32(out + 4, s1);
store_be32(out + 8, s2);
store_be32(out + 12, s3);
}
// Portable CTR pipeline: encrypt blocks sequentially
void aes_ctr_pipeline_portable(const uint32_t *rk, int rounds, const uint8_t *in, uint8_t *out, size_t blocks,
uint8_t ctr[16])
{
uint8_t keystream[16];
for (size_t i = 0; i < blocks; ++i)
{
aes_encrypt_block_portable(rk, rounds, ctr, keystream);
for (size_t j = 0; j < 16; ++j)
{
out[i * 16 + j] = in[i * 16 + j] ^ keystream[j];
}
increment_be32(ctr);
}
}
} // namespace internal
} // namespace tinyaes

116
src/backend/aes_vaes.cpp Normal file
View File

@@ -0,0 +1,116 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// VAES (AVX-512) backend: 4-block-wide CTR pipeline using 512-bit operations
#if defined(__x86_64__) || defined(_M_X64)
#include "internal/aes_impl.h"
#include "internal/endian.h"
#include <cstring>
#include <immintrin.h>
namespace tinyaes
{
namespace internal
{
void aes_ctr_pipeline_vaes(const uint32_t *rk, int rounds, const uint8_t *in, uint8_t *out, size_t blocks,
uint8_t ctr[16])
{
const __m128i *rk128 = reinterpret_cast<const __m128i *>(rk);
const __m128i bswap_mask =
_mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
__m128i ctr_block = _mm_loadu_si128(reinterpret_cast<const __m128i *>(ctr));
// Process 4 blocks at a time using ZMM registers
size_t i = 0;
for (; i + 4 <= blocks; i += 4)
{
// Generate 4 counter values
__m128i ctr_le = _mm_shuffle_epi8(ctr_block, bswap_mask);
const __m128i one = _mm_set_epi32(0, 0, 0, 1);
__m128i c0 = ctr_block;
__m128i c1_le = _mm_add_epi64(ctr_le, one);
__m128i c2_le = _mm_add_epi64(c1_le, one);
__m128i c3_le = _mm_add_epi64(c2_le, one);
__m128i c1 = _mm_shuffle_epi8(c1_le, bswap_mask);
__m128i c2 = _mm_shuffle_epi8(c2_le, bswap_mask);
__m128i c3 = _mm_shuffle_epi8(c3_le, bswap_mask);
// Pack into ZMM
__m512i ctrs = _mm512_inserti32x4(
_mm512_inserti32x4(
_mm512_inserti32x4(_mm512_castsi128_si512(c0), c1, 1),
c2, 2),
c3, 3);
// Broadcast round key and XOR
__m512i state = _mm512_xor_si512(ctrs, _mm512_broadcast_i32x4(rk128[0]));
// Middle rounds
for (int r = 1; r < rounds; ++r)
{
state = _mm512_aesenc_epi128(state, _mm512_broadcast_i32x4(rk128[r]));
}
state = _mm512_aesenclast_epi128(state, _mm512_broadcast_i32x4(rk128[rounds]));
// XOR with plaintext
__m512i pt = _mm512_loadu_si512(in + i * 16);
_mm512_storeu_si512(out + i * 16, _mm512_xor_si512(state, pt));
// Advance counter by 4
__m128i next_le = _mm_add_epi64(c3_le, one);
ctr_block = _mm_shuffle_epi8(next_le, bswap_mask);
}
// Remaining blocks: fall back to single-block AES-NI
for (; i < blocks; ++i)
{
__m128i b = _mm_xor_si128(ctr_block, rk128[0]);
for (int r = 1; r < rounds; ++r)
{
b = _mm_aesenc_si128(b, rk128[r]);
}
b = _mm_aesenclast_si128(b, rk128[rounds]);
__m128i p = _mm_loadu_si128(reinterpret_cast<const __m128i *>(in + i * 16));
_mm_storeu_si128(reinterpret_cast<__m128i *>(out + i * 16), _mm_xor_si128(b, p));
__m128i ctr_le = _mm_shuffle_epi8(ctr_block, bswap_mask);
ctr_le = _mm_add_epi64(ctr_le, _mm_set_epi32(0, 0, 0, 1));
ctr_block = _mm_shuffle_epi8(ctr_le, bswap_mask);
}
_mm_storeu_si128(reinterpret_cast<__m128i *>(ctr), ctr_block);
}
} // namespace internal
} // namespace tinyaes
#endif // x86_64

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// ARM PMULL GHASH implementation (vmull_p64)
#if defined(__aarch64__) || defined(_M_ARM64)
#include "internal/ghash.h"
#include <cstring>
#if defined(_MSC_VER)
#include <arm64_neon.h>
#else
#include <arm_neon.h>
#endif
namespace tinyaes
{
namespace internal
{
// GF(2^128) multiplication using PMULL (carry-less multiply)
static inline uint8x16_t gf128_mul_pmull(uint8x16_t a, uint8x16_t b)
{
// Reflect byte order for GCM convention
poly64_t a_lo = vgetq_lane_p64(vreinterpretq_p64_u8(a), 0);
poly64_t a_hi = vgetq_lane_p64(vreinterpretq_p64_u8(a), 1);
poly64_t b_lo = vgetq_lane_p64(vreinterpretq_p64_u8(b), 0);
poly64_t b_hi = vgetq_lane_p64(vreinterpretq_p64_u8(b), 1);
// Karatsuba multiplication
poly128_t lo = vmull_p64(a_lo, b_lo);
poly128_t hi = vmull_p64(a_hi, b_hi);
poly128_t mid0 = vmull_p64(a_lo, b_hi);
poly128_t mid1 = vmull_p64(a_hi, b_lo);
uint8x16_t lo_v = vreinterpretq_u8_p128(lo);
uint8x16_t hi_v = vreinterpretq_u8_p128(hi);
uint8x16_t mid = veorq_u8(vreinterpretq_u8_p128(mid0), vreinterpretq_u8_p128(mid1));
// Combine: shift mid and XOR into lo/hi
uint8x16_t mid_lo = vextq_u8(vdupq_n_u8(0), mid, 8);
uint8x16_t mid_hi = vextq_u8(mid, vdupq_n_u8(0), 8);
lo_v = veorq_u8(lo_v, mid_lo);
hi_v = veorq_u8(hi_v, mid_hi);
// Reduction modulo x^128 + x^7 + x^2 + x + 1
// Using the reflected reduction polynomial 0xC2...01
poly64_t r = (poly64_t)0xC200000000000000ULL;
poly128_t t1 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(hi_v), 0), r);
uint8x16_t f = veorq_u8(lo_v, vreinterpretq_u8_p128(t1));
uint8x16_t f_swap = vextq_u8(f, f, 8);
poly128_t t2 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(f_swap), 0), r);
return veorq_u8(veorq_u8(f_swap, vreinterpretq_u8_p128(t2)), hi_v);
}
void ghash_arm_ce(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16])
{
// GCM uses big-endian bit reflection; ARM PMULL operates on reflected bits
// The byte reversal handles the endianness conversion
uint8x16_t rev_mask = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
uint8x16_t h = vqtbl1q_u8(vld1q_u8(H), rev_mask);
uint8x16_t y = vqtbl1q_u8(vld1q_u8(Y), rev_mask);
size_t full_blocks = data_len / 16;
for (size_t i = 0; i < full_blocks; ++i)
{
uint8x16_t d = vqtbl1q_u8(vld1q_u8(data + i * 16), rev_mask);
y = veorq_u8(y, d);
y = gf128_mul_pmull(y, h);
}
size_t remainder = data_len % 16;
if (remainder > 0)
{
uint8_t padded[16] = {0};
std::memcpy(padded, data + full_blocks * 16, remainder);
uint8x16_t d = vqtbl1q_u8(vld1q_u8(padded), rev_mask);
y = veorq_u8(y, d);
y = gf128_mul_pmull(y, h);
}
vst1q_u8(Y, vqtbl1q_u8(y, rev_mask));
}
} // namespace internal
} // namespace tinyaes
#endif // aarch64

View File

@@ -0,0 +1,128 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// PCLMULQDQ GHASH implementation for x86_64
#if defined(__x86_64__) || defined(_M_X64)
#include "internal/ghash.h"
#include <cstring>
#include <wmmintrin.h>
#include <smmintrin.h>
#include <emmintrin.h>
namespace tinyaes
{
namespace internal
{
// GF(2^128) multiplication using PCLMULQDQ with Karatsuba method
// and two-phase reduction (Intel CLMUL whitepaper "Algorithm 5").
// GCM polynomial: x^128 + x^7 + x^2 + x + 1
// Reflected: x^128 + x^127 + x^126 + x^121 + 1
// Low 64 bits of reflected poly (excl. x^128 and x^0): 0xC200000000000000
static inline void gf128_mul_pclmul(const __m128i a, const __m128i b, __m128i &result)
{
// Karatsuba carry-less multiplication → 256-bit product [hi:lo]
__m128i lo = _mm_clmulepi64_si128(a, b, 0x00);
__m128i hi = _mm_clmulepi64_si128(a, b, 0x11);
__m128i mid0 = _mm_clmulepi64_si128(a, b, 0x01);
__m128i mid1 = _mm_clmulepi64_si128(a, b, 0x10);
__m128i mid = _mm_xor_si128(mid0, mid1);
hi = _mm_xor_si128(hi, _mm_srli_si128(mid, 8));
lo = _mm_xor_si128(lo, _mm_slli_si128(mid, 8));
// Two-phase reduction. The constant 0xC200000000000000 must be in
// the LOW 64 bits of the __m128i so that selector 0x00 picks it up.
const __m128i poly =
_mm_set_epi64x(0, static_cast<long long>(0xC200000000000000ULL));
// Phase 1: multiply lo's low 64 bits by the polynomial constant
__m128i t1 = _mm_clmulepi64_si128(lo, poly, 0x00);
// Swap lo's 64-bit halves (handles the implicit x^64 term) and XOR
lo = _mm_xor_si128(_mm_shuffle_epi32(lo, 0x4E), t1);
// Phase 2: repeat on the intermediate result
__m128i t2 = _mm_clmulepi64_si128(lo, poly, 0x00);
lo = _mm_xor_si128(_mm_shuffle_epi32(lo, 0x4E), t2);
// Combine with the high half
result = _mm_xor_si128(lo, hi);
}
void ghash_pclmulqdq(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16])
{
const __m128i bswap_mask =
_mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
__m128i h = _mm_shuffle_epi8(_mm_loadu_si128(reinterpret_cast<const __m128i *>(H)), bswap_mask);
// Pre-shift H left by 1 bit in the reflected domain to compensate for
// the x factor introduced by CLMUL's polynomial multiplication convention.
// This is the standard GHASH-CLMUL preprocessing step.
__m128i h_carry = _mm_srli_epi64(h, 63); // bit 63 of each half
__m128i h_carry_low = _mm_slli_si128(h_carry, 8); // carry from low to high
__m128i h_overflow = _mm_srli_si128(h_carry, 8); // overflow bit (was bit 127)
h = _mm_or_si128(_mm_slli_epi64(h, 1), h_carry_low);
// If bit 127 was set, reduce: x^128 mod p_reflected = x^127+x^126+x^121+1
const __m128i reduce_const =
_mm_set_epi64x(static_cast<long long>(0xC200000000000000ULL), 1);
// Broadcast overflow to both 64-bit halves for a full 128-bit mask
__m128i reduce_mask = _mm_shuffle_epi32(
_mm_sub_epi64(_mm_setzero_si128(), h_overflow), 0x44); // [lo, lo]
h = _mm_xor_si128(h, _mm_and_si128(reduce_const, reduce_mask));
__m128i y = _mm_shuffle_epi8(_mm_loadu_si128(reinterpret_cast<const __m128i *>(Y)), bswap_mask);
size_t full_blocks = data_len / 16;
for (size_t i = 0; i < full_blocks; ++i)
{
__m128i d = _mm_shuffle_epi8(
_mm_loadu_si128(reinterpret_cast<const __m128i *>(data + i * 16)), bswap_mask);
y = _mm_xor_si128(y, d);
gf128_mul_pclmul(y, h, y);
}
// Handle partial block
size_t remainder = data_len % 16;
if (remainder > 0)
{
uint8_t padded[16] = {0};
std::memcpy(padded, data + full_blocks * 16, remainder);
__m128i d = _mm_shuffle_epi8(_mm_loadu_si128(reinterpret_cast<const __m128i *>(padded)), bswap_mask);
y = _mm_xor_si128(y, d);
gf128_mul_pclmul(y, h, y);
}
_mm_storeu_si128(reinterpret_cast<__m128i *>(Y), _mm_shuffle_epi8(y, bswap_mask));
}
} // namespace internal
} // namespace tinyaes
#endif // x86_64

View File

@@ -0,0 +1,113 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Portable constant-time GHASH implementation in GF(2^128).
// MSB-first convention, reduction polynomial x^128 + x^7 + x^2 + x + 1 (0xE1).
#include "internal/ghash.h"
#include "internal/endian.h"
namespace tinyaes
{
namespace internal
{
// GF(2^128) multiplication: Z = X * Y in GF(2^128)
// Uses bit-by-bit algorithm (constant-time: no data-dependent branches).
static void gf128_mul(const uint8_t X[16], const uint8_t Y[16], uint8_t Z[16])
{
uint64_t Xh = load_be64(X);
uint64_t Xl = load_be64(X + 8);
uint64_t Zh = 0, Zl = 0;
uint64_t Vh = load_be64(Y);
uint64_t Vl = load_be64(Y + 8);
for (int i = 0; i < 128; ++i)
{
// If bit i of X is set, XOR V into Z
// Constant-time: compute mask from bit value
uint64_t xi;
if (i < 64)
xi = (Xh >> (63 - i)) & 1;
else
xi = (Xl >> (127 - i)) & 1;
uint64_t mask = static_cast<uint64_t>(0) - xi; // 0 or 0xFFFFFFFFFFFFFFFF
Zh ^= (Vh & mask);
Zl ^= (Vl & mask);
// V = V >> 1 in GF(2^128) with reduction
uint64_t carry = Vl & 1;
Vl = (Vl >> 1) | (Vh << 63);
Vh >>= 1;
// If carry, XOR with R = 0xE1 << 56 (0xE100000000000000)
uint64_t reduce_mask = static_cast<uint64_t>(0) - carry;
Vh ^= (UINT64_C(0xE100000000000000) & reduce_mask);
}
store_be64(Z, Zh);
store_be64(Z + 8, Zl);
}
void ghash_portable(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16])
{
// Process complete 16-byte blocks
size_t full_blocks = data_len / 16;
for (size_t i = 0; i < full_blocks; ++i)
{
// Y = (Y XOR data_block) * H
for (int j = 0; j < 16; ++j)
{
Y[j] ^= data[i * 16 + static_cast<size_t>(j)];
}
uint8_t tmp[16];
gf128_mul(Y, H, tmp);
for (int j = 0; j < 16; ++j)
{
Y[j] = tmp[j];
}
}
// Handle final partial block (zero-padded)
size_t remainder = data_len % 16;
if (remainder > 0)
{
for (size_t j = 0; j < remainder; ++j)
{
Y[j] ^= data[full_blocks * 16 + j];
}
// Remaining bytes stay as-is (zero-padded XOR is identity)
uint8_t tmp[16];
gf128_mul(Y, H, tmp);
for (int j = 0; j < 16; ++j)
{
Y[j] = tmp[j];
}
}
}
} // namespace internal
} // namespace tinyaes

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// VPCLMULQDQ (AVX-512) GHASH — delegates to PCLMULQDQ for now.
// A full AVX-512 implementation would process 4 blocks in parallel using
// powers of H (H^4, H^3, H^2, H) and aggregate, but correctness-first.
#if defined(__x86_64__) || defined(_M_X64)
#include "internal/ghash.h"
namespace tinyaes
{
namespace internal
{
void ghash_vpclmulqdq(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16])
{
// Delegate to PCLMULQDQ implementation for correctness
ghash_pclmulqdq(H, data, data_len, Y);
}
} // namespace internal
} // namespace tinyaes
#endif // x86_64

300
src/cbc.cpp Normal file
View File

@@ -0,0 +1,300 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "tinyaes/cbc.h"
#include "internal/aes_impl.h"
#include <cstring>
namespace tinyaes
{
Result cbc_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext)
{
int rounds = internal::aes_rounds(key.size());
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.size() != 16)
return Result::InvalidInput;
if (plaintext.empty() || (plaintext.size() % 16) != 0)
return Result::InvalidInput;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
key_expand(key.data(), key.size(), rk);
auto encrypt_block = internal::get_encrypt_block();
ciphertext.resize(plaintext.size());
uint8_t block[16];
std::memcpy(block, iv.data(), 16);
for (size_t i = 0; i < plaintext.size(); i += 16)
{
// XOR plaintext block with previous ciphertext (or IV)
for (int j = 0; j < 16; ++j)
{
block[j] ^= plaintext[i + static_cast<size_t>(j)];
}
encrypt_block(rk, rounds, block, ciphertext.data() + i);
std::memcpy(block, ciphertext.data() + i, 16);
}
secure_zero(rk, sizeof(rk));
secure_zero(block, sizeof(block));
return Result::Success;
}
Result cbc_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &plaintext)
{
int rounds = internal::aes_rounds(key.size());
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.size() != 16)
return Result::InvalidInput;
if (ciphertext.empty() || (ciphertext.size() % 16) != 0)
return Result::InvalidInput;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
key_expand(key.data(), key.size(), rk);
auto decrypt_block = internal::get_decrypt_block();
plaintext.resize(ciphertext.size());
const uint8_t *prev = iv.data();
for (size_t i = 0; i < ciphertext.size(); i += 16)
{
decrypt_block(rk, rounds, ciphertext.data() + i, plaintext.data() + i);
// XOR with previous ciphertext block (or IV)
for (int j = 0; j < 16; ++j)
{
plaintext[i + static_cast<size_t>(j)] ^= prev[j];
}
prev = ciphertext.data() + i;
}
secure_zero(rk, sizeof(rk));
return Result::Success;
}
// PKCS#7 padding: append N bytes of value N where N = 16 - (len % 16)
// If input is already block-aligned, a full padding block is added.
Result cbc_encrypt_pkcs7(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext)
{
size_t pad_len = 16 - (plaintext.size() % 16);
std::vector<uint8_t> padded(plaintext.size() + pad_len);
std::memcpy(padded.data(), plaintext.data(), plaintext.size());
std::memset(padded.data() + plaintext.size(), static_cast<int>(pad_len), pad_len);
auto result = cbc_encrypt(key, iv, padded, ciphertext);
secure_zero(padded.data(), padded.size());
return result;
}
// Constant-time PKCS#7 unpadding
Result cbc_decrypt_pkcs7(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &plaintext)
{
std::vector<uint8_t> decrypted;
auto result = cbc_decrypt(key, iv, ciphertext, decrypted);
if (result != Result::Success)
return result;
if (decrypted.empty())
return Result::InvalidPadding;
// Constant-time PKCS#7 validation: scan entire last block
uint8_t pad_val = decrypted.back();
if (pad_val == 0 || pad_val > 16)
return Result::InvalidPadding;
// Verify all padding bytes match (constant-time)
volatile uint8_t bad = 0;
size_t start = decrypted.size() - pad_val;
for (size_t i = start; i < decrypted.size(); ++i)
{
bad |= static_cast<uint8_t>(decrypted[i] ^ pad_val);
}
if (bad != 0)
{
secure_zero(decrypted.data(), decrypted.size());
return Result::InvalidPadding;
}
plaintext.assign(decrypted.begin(), decrypted.begin() + static_cast<ptrdiff_t>(start));
secure_zero(decrypted.data(), decrypted.size());
return Result::Success;
}
} // namespace tinyaes
// C API implementations
extern "C" int tinyaes_cbc_encrypt(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
size_t ciphertext_len)
{
if (!key || !iv || !plaintext || !ciphertext)
return TINYAES_ERROR_INVALID_INPUT;
if (ciphertext_len < plaintext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + 16);
std::vector<uint8_t> pt(plaintext, plaintext + plaintext_len);
std::vector<uint8_t> ct;
auto result = tinyaes::cbc_encrypt(k, v, pt, ct);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
return static_cast<int>(result);
std::memcpy(ciphertext, ct.data(), ct.size());
return TINYAES_SUCCESS;
}
extern "C" int tinyaes_cbc_decrypt(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t *plaintext,
size_t plaintext_len)
{
if (!key || !iv || !ciphertext || !plaintext)
return TINYAES_ERROR_INVALID_INPUT;
if (plaintext_len < ciphertext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + 16);
std::vector<uint8_t> ct(ciphertext, ciphertext + ciphertext_len);
std::vector<uint8_t> pt;
auto result = tinyaes::cbc_decrypt(k, v, ct, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
return static_cast<int>(result);
std::memcpy(plaintext, pt.data(), pt.size());
return TINYAES_SUCCESS;
}
extern "C" int tinyaes_cbc_encrypt_pkcs7(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
size_t *ciphertext_len)
{
if (!key || !iv || !plaintext || !ciphertext || !ciphertext_len)
return TINYAES_ERROR_INVALID_INPUT;
size_t padded_len = plaintext_len + (16 - (plaintext_len % 16));
if (*ciphertext_len < padded_len)
{
*ciphertext_len = padded_len;
return TINYAES_ERROR_BUFFER_TOO_SMALL;
}
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + 16);
std::vector<uint8_t> pt(plaintext, plaintext + plaintext_len);
std::vector<uint8_t> ct;
auto result = tinyaes::cbc_encrypt_pkcs7(k, v, pt, ct);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
return static_cast<int>(result);
std::memcpy(ciphertext, ct.data(), ct.size());
*ciphertext_len = ct.size();
return TINYAES_SUCCESS;
}
extern "C" int tinyaes_cbc_decrypt_pkcs7(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t *plaintext,
size_t *plaintext_len)
{
if (!key || !iv || !ciphertext || !plaintext || !plaintext_len)
return TINYAES_ERROR_INVALID_INPUT;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + 16);
std::vector<uint8_t> ct(ciphertext, ciphertext + ciphertext_len);
std::vector<uint8_t> pt;
auto result = tinyaes::cbc_decrypt_pkcs7(k, v, ct, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
return static_cast<int>(result);
if (*plaintext_len < pt.size())
{
*plaintext_len = pt.size();
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_ERROR_BUFFER_TOO_SMALL;
}
std::memcpy(plaintext, pt.data(), pt.size());
*plaintext_len = pt.size();
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_SUCCESS;
}

124
src/cpuid.cpp Normal file
View File

@@ -0,0 +1,124 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "cpuid.h"
#if defined(__x86_64__) || defined(_M_X64)
#if defined(_MSC_VER) && !defined(__clang__)
#include <intrin.h>
#endif
#endif
#if defined(__aarch64__) && defined(__linux__)
#include <asm/hwcap.h>
#include <sys/auxv.h>
#endif
namespace tinyaes
{
namespace internal
{
#if defined(__x86_64__) || defined(_M_X64)
static void cpuid(uint32_t leaf, uint32_t subleaf, uint32_t &eax, uint32_t &ebx, uint32_t &ecx, uint32_t &edx)
{
#if defined(_MSC_VER) && !defined(__clang__)
int regs[4];
__cpuidex(regs, static_cast<int>(leaf), static_cast<int>(subleaf));
eax = static_cast<uint32_t>(regs[0]);
ebx = static_cast<uint32_t>(regs[1]);
ecx = static_cast<uint32_t>(regs[2]);
edx = static_cast<uint32_t>(regs[3]);
#else
__asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(leaf), "c"(subleaf));
#endif
}
CpuFeatures detect_cpu_features()
{
CpuFeatures f;
uint32_t eax, ebx, ecx, edx;
// Check max leaf
cpuid(0, 0, eax, ebx, ecx, edx);
uint32_t max_leaf = eax;
// Leaf 1: AES-NI and PCLMULQDQ
if (max_leaf >= 1)
{
cpuid(1, 0, eax, ebx, ecx, edx);
f.aesni = (ecx & (1u << 25)) != 0;
f.pclmulqdq = (ecx & (1u << 1)) != 0;
}
// Leaf 7, subleaf 0: AVX-512F, VAES, VPCLMULQDQ
if (max_leaf >= 7)
{
cpuid(7, 0, eax, ebx, ecx, edx);
f.avx512f = (ebx & (1u << 16)) != 0;
f.vaes = (ecx & (1u << 9)) != 0;
f.vpclmulqdq = (ecx & (1u << 10)) != 0;
}
return f;
}
#elif defined(__aarch64__) || defined(_M_ARM64)
CpuFeatures detect_cpu_features()
{
CpuFeatures f;
#if defined(__APPLE__)
// Apple Silicon (M1+) always has AES and PMULL extensions
f.arm_aes = true;
f.arm_pmull = true;
#elif defined(__linux__)
unsigned long hwcap = getauxval(AT_HWCAP);
f.arm_aes = (hwcap & HWCAP_AES) != 0;
f.arm_pmull = (hwcap & HWCAP_PMULL) != 0;
#elif defined(_M_ARM64)
// Windows on ARM64: AES CE is baseline
f.arm_aes = true;
f.arm_pmull = true;
#endif
return f;
}
#else
// Non-x86, non-ARM64: no SIMD features
CpuFeatures detect_cpu_features()
{
return CpuFeatures {};
}
#endif
} // namespace internal
} // namespace tinyaes

52
src/cpuid.h Normal file
View File

@@ -0,0 +1,52 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstdint>
namespace tinyaes
{
namespace internal
{
struct CpuFeatures
{
// x86_64
bool aesni = false;
bool pclmulqdq = false;
bool avx512f = false;
bool vaes = false;
bool vpclmulqdq = false;
// aarch64
bool arm_aes = false;
bool arm_pmull = false;
};
CpuFeatures detect_cpu_features();
} // namespace internal
} // namespace tinyaes

118
src/ctr.cpp Normal file
View File

@@ -0,0 +1,118 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "tinyaes/ctr.h"
#include "internal/aes_impl.h"
#include "internal/endian.h"
#include <cstring>
namespace tinyaes
{
Result ctr_crypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &input,
std::vector<uint8_t> &output)
{
int rounds = internal::aes_rounds(key.size());
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.size() != 16)
return Result::InvalidInput;
if (input.empty())
return Result::InvalidInput;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
key_expand(key.data(), key.size(), rk);
output.resize(input.size());
uint8_t ctr[16];
std::memcpy(ctr, iv.data(), 16);
// Process full blocks via pipeline
size_t full_blocks = input.size() / 16;
size_t remainder = input.size() % 16;
if (full_blocks > 0)
{
auto ctr_pipeline = internal::get_ctr_pipeline();
ctr_pipeline(rk, rounds, input.data(), output.data(), full_blocks, ctr);
}
// Handle final partial block
if (remainder > 0)
{
uint8_t keystream[16];
auto encrypt_block = internal::get_encrypt_block();
encrypt_block(rk, rounds, ctr, keystream);
size_t offset = full_blocks * 16;
for (size_t i = 0; i < remainder; ++i)
{
output[offset + i] = input[offset + i] ^ keystream[i];
}
secure_zero(keystream, sizeof(keystream));
}
secure_zero(rk, sizeof(rk));
secure_zero(ctr, sizeof(ctr));
return Result::Success;
}
} // namespace tinyaes
extern "C" int tinyaes_ctr_crypt(
const uint8_t *key,
size_t key_len,
const uint8_t iv[16],
const uint8_t *input,
size_t input_len,
uint8_t *output,
size_t output_len)
{
if (!key || !iv || !input || !output)
return TINYAES_ERROR_INVALID_INPUT;
if (output_len < input_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + 16);
std::vector<uint8_t> in(input, input + input_len);
std::vector<uint8_t> out;
auto result = tinyaes::ctr_crypt(k, v, in, out);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
return static_cast<int>(result);
std::memcpy(output, out.data(), out.size());
return TINYAES_SUCCESS;
}

143
src/ecb.cpp Normal file
View File

@@ -0,0 +1,143 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "tinyaes/ecb.h"
#include "internal/aes_impl.h"
#include <cstring>
namespace tinyaes
{
Result ecb_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext)
{
int rounds = internal::aes_rounds(key.size());
if (rounds == 0)
return Result::InvalidKeySize;
if (plaintext.empty() || (plaintext.size() % 16) != 0)
return Result::InvalidInput;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
key_expand(key.data(), key.size(), rk);
auto encrypt_block = internal::get_encrypt_block();
ciphertext.resize(plaintext.size());
for (size_t i = 0; i < plaintext.size(); i += 16)
{
encrypt_block(rk, rounds, plaintext.data() + i, ciphertext.data() + i);
}
secure_zero(rk, sizeof(rk));
return Result::Success;
}
Result ecb_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &plaintext)
{
int rounds = internal::aes_rounds(key.size());
if (rounds == 0)
return Result::InvalidKeySize;
if (ciphertext.empty() || (ciphertext.size() % 16) != 0)
return Result::InvalidInput;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
key_expand(key.data(), key.size(), rk);
auto decrypt_block = internal::get_decrypt_block();
plaintext.resize(ciphertext.size());
for (size_t i = 0; i < ciphertext.size(); i += 16)
{
decrypt_block(rk, rounds, ciphertext.data() + i, plaintext.data() + i);
}
secure_zero(rk, sizeof(rk));
return Result::Success;
}
} // namespace tinyaes
extern "C" int tinyaes_ecb_encrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
size_t ciphertext_len)
{
if (!key || !plaintext || !ciphertext)
return TINYAES_ERROR_INVALID_INPUT;
if (ciphertext_len < plaintext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> pt(plaintext, plaintext + plaintext_len);
std::vector<uint8_t> ct;
auto result = tinyaes::ecb_encrypt(k, pt, ct);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
return static_cast<int>(result);
std::memcpy(ciphertext, ct.data(), ct.size());
return TINYAES_SUCCESS;
}
extern "C" int tinyaes_ecb_decrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t *plaintext,
size_t plaintext_len)
{
if (!key || !ciphertext || !plaintext)
return TINYAES_ERROR_INVALID_INPUT;
if (plaintext_len < ciphertext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> ct(ciphertext, ciphertext + ciphertext_len);
std::vector<uint8_t> pt;
auto result = tinyaes::ecb_decrypt(k, ct, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
return static_cast<int>(result);
std::memcpy(plaintext, pt.data(), pt.size());
return TINYAES_SUCCESS;
}

354
src/gcm.cpp Normal file
View File

@@ -0,0 +1,354 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "tinyaes/gcm.h"
#include "internal/aes_impl.h"
#include "internal/endian.h"
#include "internal/ghash.h"
#include <cstring>
namespace tinyaes
{
// Compute J0 from IV per NIST SP 800-38D Section 7.1
static void compute_j0(
const uint8_t *iv,
size_t iv_len,
const uint8_t H[16],
internal::ghash_fn ghash,
uint8_t J0[16])
{
if (iv_len == 12)
{
// If len(IV) = 96 bits: J0 = IV || 0^31 || 1
std::memcpy(J0, iv, 12);
J0[12] = 0;
J0[13] = 0;
J0[14] = 0;
J0[15] = 1;
}
else
{
// J0 = GHASH_H(IV || 0^s || len(IV)_64)
// where s = 128*ceil(len(IV)/128) - len(IV) + 64
std::memset(J0, 0, 16);
// GHASH the IV data (handles padding internally)
ghash(H, iv, iv_len, J0);
// Pad to 16-byte boundary, then add 8 zero bytes + 8-byte big-endian bit length
uint8_t len_block[16] = {0};
uint64_t iv_bits = static_cast<uint64_t>(iv_len) * 8;
internal::store_be64(len_block + 8, iv_bits);
ghash(H, len_block, 16, J0);
}
}
// Compute GCM tag per NIST SP 800-38D
static void compute_tag(
const uint32_t *rk,
int rounds,
const uint8_t H[16],
const uint8_t J0[16],
const uint8_t *aad,
size_t aad_len,
const uint8_t *ciphertext,
size_t ciphertext_len,
internal::ghash_fn ghash,
internal::encrypt_block_fn encrypt_block,
uint8_t tag[16])
{
uint8_t S[16] = {0};
// GHASH AAD
if (aad_len > 0)
{
ghash(H, aad, aad_len, S);
}
// GHASH ciphertext
if (ciphertext_len > 0)
{
ghash(H, ciphertext, ciphertext_len, S);
}
// Append length block: [len(A)_64 || len(C)_64] in bits
uint8_t len_block[16];
internal::store_be64(len_block, static_cast<uint64_t>(aad_len) * 8);
internal::store_be64(len_block + 8, static_cast<uint64_t>(ciphertext_len) * 8);
ghash(H, len_block, 16, S);
// T = S XOR E_K(J0)
uint8_t E_J0[16];
encrypt_block(rk, rounds, J0, E_J0);
for (int i = 0; i < 16; ++i)
{
tag[i] = S[i] ^ E_J0[i];
}
}
Result gcm_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &aad,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &tag)
{
int rounds = internal::aes_rounds(key.size());
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.empty())
return Result::InvalidInput;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
key_expand(key.data(), key.size(), rk);
auto encrypt_block = internal::get_encrypt_block();
auto ghash = internal::get_ghash();
// Compute H = E_K(0^128)
uint8_t H[16] = {0};
uint8_t zero_block[16] = {0};
encrypt_block(rk, rounds, zero_block, H);
// Compute J0
uint8_t J0[16];
compute_j0(iv.data(), iv.size(), H, ghash, J0);
// Encrypt plaintext using CTR mode starting from inc32(J0)
ciphertext.resize(plaintext.size());
if (!plaintext.empty())
{
uint8_t ctr[16];
std::memcpy(ctr, J0, 16);
internal::increment_be32(ctr);
size_t full_blocks = plaintext.size() / 16;
size_t remainder = plaintext.size() % 16;
if (full_blocks > 0)
{
auto ctr_pipeline = internal::get_ctr_pipeline();
ctr_pipeline(rk, rounds, plaintext.data(), ciphertext.data(), full_blocks, ctr);
}
if (remainder > 0)
{
uint8_t keystream[16];
encrypt_block(rk, rounds, ctr, keystream);
size_t offset = full_blocks * 16;
for (size_t i = 0; i < remainder; ++i)
{
ciphertext[offset + i] = plaintext[offset + i] ^ keystream[i];
}
secure_zero(keystream, sizeof(keystream));
}
}
// Compute authentication tag
tag.resize(16);
compute_tag(rk, rounds, H, J0, aad.data(), aad.size(), ciphertext.data(), ciphertext.size(), ghash,
encrypt_block, tag.data());
secure_zero(rk, sizeof(rk));
secure_zero(H, sizeof(H));
secure_zero(J0, sizeof(J0));
return Result::Success;
}
Result gcm_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &aad,
const std::vector<uint8_t> &ciphertext,
const std::vector<uint8_t> &tag,
std::vector<uint8_t> &plaintext)
{
int rounds = internal::aes_rounds(key.size());
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.empty())
return Result::InvalidInput;
if (tag.size() != 16)
return Result::InvalidInput;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
key_expand(key.data(), key.size(), rk);
auto encrypt_block = internal::get_encrypt_block();
auto ghash = internal::get_ghash();
// Compute H = E_K(0^128)
uint8_t H[16] = {0};
uint8_t zero_block[16] = {0};
encrypt_block(rk, rounds, zero_block, H);
// Compute J0
uint8_t J0[16];
compute_j0(iv.data(), iv.size(), H, ghash, J0);
// Verify tag BEFORE decrypting (decrypt into temp buffer)
uint8_t computed_tag[16];
compute_tag(rk, rounds, H, J0, aad.data(), aad.size(), ciphertext.data(), ciphertext.size(), ghash,
encrypt_block, computed_tag);
if (!constant_time_equal(computed_tag, tag.data(), 16))
{
secure_zero(rk, sizeof(rk));
secure_zero(H, sizeof(H));
secure_zero(J0, sizeof(J0));
secure_zero(computed_tag, sizeof(computed_tag));
return Result::AuthFailed;
}
// Tag verified — decrypt ciphertext using CTR mode
plaintext.resize(ciphertext.size());
if (!ciphertext.empty())
{
uint8_t ctr[16];
std::memcpy(ctr, J0, 16);
internal::increment_be32(ctr);
size_t full_blocks = ciphertext.size() / 16;
size_t remainder = ciphertext.size() % 16;
if (full_blocks > 0)
{
auto ctr_pipeline = internal::get_ctr_pipeline();
ctr_pipeline(rk, rounds, ciphertext.data(), plaintext.data(), full_blocks, ctr);
}
if (remainder > 0)
{
uint8_t keystream[16];
encrypt_block(rk, rounds, ctr, keystream);
size_t offset = full_blocks * 16;
for (size_t i = 0; i < remainder; ++i)
{
plaintext[offset + i] = ciphertext[offset + i] ^ keystream[i];
}
secure_zero(keystream, sizeof(keystream));
}
}
secure_zero(rk, sizeof(rk));
secure_zero(H, sizeof(H));
secure_zero(J0, sizeof(J0));
secure_zero(computed_tag, sizeof(computed_tag));
return Result::Success;
}
} // namespace tinyaes
extern "C" int tinyaes_gcm_encrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *iv,
size_t iv_len,
const uint8_t *aad,
size_t aad_len,
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t tag[16])
{
if (!key || !iv || !tag)
return TINYAES_ERROR_INVALID_INPUT;
if (plaintext_len > 0 && (!plaintext || !ciphertext))
return TINYAES_ERROR_INVALID_INPUT;
if (ciphertext_len < plaintext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + iv_len);
std::vector<uint8_t> a(aad ? aad : key, aad ? aad + aad_len : key);
if (!aad)
a.clear();
std::vector<uint8_t> pt(plaintext ? plaintext : key, plaintext ? plaintext + plaintext_len : key);
if (!plaintext)
pt.clear();
std::vector<uint8_t> ct, t;
auto result = tinyaes::gcm_encrypt(k, v, a, pt, ct, t);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
return static_cast<int>(result);
if (!ct.empty())
std::memcpy(ciphertext, ct.data(), ct.size());
std::memcpy(tag, t.data(), 16);
return TINYAES_SUCCESS;
}
extern "C" int tinyaes_gcm_decrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *iv,
size_t iv_len,
const uint8_t *aad,
size_t aad_len,
const uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t *plaintext,
size_t plaintext_len,
const uint8_t tag[16])
{
if (!key || !iv || !tag)
return TINYAES_ERROR_INVALID_INPUT;
if (ciphertext_len > 0 && (!ciphertext || !plaintext))
return TINYAES_ERROR_INVALID_INPUT;
if (plaintext_len < ciphertext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + iv_len);
std::vector<uint8_t> a(aad ? aad : key, aad ? aad + aad_len : key);
if (!aad)
a.clear();
std::vector<uint8_t> ct(ciphertext ? ciphertext : key, ciphertext ? ciphertext + ciphertext_len : key);
if (!ciphertext)
ct.clear();
std::vector<uint8_t> t(tag, tag + 16);
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(k, v, a, ct, t, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
return static_cast<int>(result);
if (!pt.empty())
std::memcpy(plaintext, pt.data(), pt.size());
return TINYAES_SUCCESS;
}

94
src/internal/aes_impl.h Normal file
View File

@@ -0,0 +1,94 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstddef>
#include <cstdint>
namespace tinyaes
{
namespace internal
{
// Maximum round keys: AES-256 has 15 round keys * 4 words = 60 words
static constexpr size_t AES_MAX_RK_WORDS = 60;
// Backend function signatures
using encrypt_block_fn = void (*)(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16]);
using decrypt_block_fn = void (*)(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16]);
using key_expand_fn = void (*)(const uint8_t *key, size_t key_len, uint32_t *rk);
using ctr_pipeline_fn = void (*)(const uint32_t *rk, int rounds, const uint8_t *in, uint8_t *out, size_t blocks,
uint8_t ctr[16]);
// Portable backend declarations
void aes_encrypt_block_portable(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16]);
void aes_decrypt_block_portable(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16]);
void aes_key_expand_portable(const uint8_t *key, size_t key_len, uint32_t *rk);
void aes_ctr_pipeline_portable(const uint32_t *rk, int rounds, const uint8_t *in, uint8_t *out, size_t blocks,
uint8_t ctr[16]);
// x86 AES-NI backend declarations
void aes_encrypt_block_aesni(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16]);
void aes_decrypt_block_aesni(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16]);
void aes_key_expand_aesni(const uint8_t *key, size_t key_len, uint32_t *rk);
void aes_ctr_pipeline_aesni(const uint32_t *rk, int rounds, const uint8_t *in, uint8_t *out, size_t blocks,
uint8_t ctr[16]);
// x86 VAES (AVX-512) backend declarations
void aes_ctr_pipeline_vaes(const uint32_t *rk, int rounds, const uint8_t *in, uint8_t *out, size_t blocks,
uint8_t ctr[16]);
// ARM CE backend declarations
void aes_encrypt_block_arm_ce(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16]);
void aes_decrypt_block_arm_ce(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16]);
void aes_ctr_pipeline_arm_ce(const uint32_t *rk, int rounds, const uint8_t *in, uint8_t *out, size_t blocks,
uint8_t ctr[16]);
// Dispatch getters (lazy-resolved via CPUID)
encrypt_block_fn get_encrypt_block();
decrypt_block_fn get_decrypt_block();
key_expand_fn get_key_expand();
ctr_pipeline_fn get_ctr_pipeline();
// Number of rounds for a given key size
inline int aes_rounds(size_t key_len)
{
switch (key_len)
{
case 16:
return 10;
case 24:
return 12;
case 32:
return 14;
default:
return 0;
}
}
} // namespace internal
} // namespace tinyaes

79
src/internal/endian.h Normal file
View File

@@ -0,0 +1,79 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstdint>
namespace tinyaes
{
namespace internal
{
inline uint32_t load_be32(const uint8_t *p)
{
return (static_cast<uint32_t>(p[0]) << 24) | (static_cast<uint32_t>(p[1]) << 16)
| (static_cast<uint32_t>(p[2]) << 8) | (static_cast<uint32_t>(p[3]));
}
inline uint64_t load_be64(const uint8_t *p)
{
return (static_cast<uint64_t>(p[0]) << 56) | (static_cast<uint64_t>(p[1]) << 48)
| (static_cast<uint64_t>(p[2]) << 40) | (static_cast<uint64_t>(p[3]) << 32)
| (static_cast<uint64_t>(p[4]) << 24) | (static_cast<uint64_t>(p[5]) << 16)
| (static_cast<uint64_t>(p[6]) << 8) | (static_cast<uint64_t>(p[7]));
}
inline void store_be32(uint8_t *p, uint32_t v)
{
p[0] = static_cast<uint8_t>(v >> 24);
p[1] = static_cast<uint8_t>(v >> 16);
p[2] = static_cast<uint8_t>(v >> 8);
p[3] = static_cast<uint8_t>(v);
}
inline void store_be64(uint8_t *p, uint64_t v)
{
p[0] = static_cast<uint8_t>(v >> 56);
p[1] = static_cast<uint8_t>(v >> 48);
p[2] = static_cast<uint8_t>(v >> 40);
p[3] = static_cast<uint8_t>(v >> 32);
p[4] = static_cast<uint8_t>(v >> 24);
p[5] = static_cast<uint8_t>(v >> 16);
p[6] = static_cast<uint8_t>(v >> 8);
p[7] = static_cast<uint8_t>(v);
}
// Increment big-endian 32-bit counter in the last 4 bytes of a 16-byte block
inline void increment_be32(uint8_t block[16])
{
uint32_t ctr = load_be32(block + 12);
++ctr;
store_be32(block + 12, ctr);
}
} // namespace internal
} // namespace tinyaes

57
src/internal/ghash.h Normal file
View File

@@ -0,0 +1,57 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstddef>
#include <cstdint>
namespace tinyaes
{
namespace internal
{
// GHASH function signature: processes AAD and ciphertext, produces 16-byte tag component
// H is the hash key (AES_K(0^128)), Y is the running accumulator
using ghash_fn = void (*)(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16]);
// Portable GHASH
void ghash_portable(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16]);
// x86 PCLMULQDQ GHASH
void ghash_pclmulqdq(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16]);
// x86 VPCLMULQDQ GHASH (AVX-512)
void ghash_vpclmulqdq(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16]);
// ARM PMULL GHASH
void ghash_arm_ce(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16]);
// Dispatch getter
ghash_fn get_ghash();
} // namespace internal
} // namespace tinyaes

66
src/iv_generate.cpp Normal file
View File

@@ -0,0 +1,66 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "tinyaes/common.h"
#if defined(_WIN32)
#include <windows.h>
#include <bcrypt.h>
#elif defined(__linux__)
#include <sys/random.h>
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <stdlib.h>
#endif
namespace tinyaes
{
int generate_iv(uint8_t *out, size_t len)
{
if (!out || len == 0)
return -1;
#if defined(_WIN32)
NTSTATUS status = BCryptGenRandom(nullptr, out, static_cast<ULONG>(len), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
return BCRYPT_SUCCESS(status) ? 0 : -1;
#elif defined(__linux__)
ssize_t ret = getrandom(out, len, 0);
return (ret == static_cast<ssize_t>(len)) ? 0 : -1;
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
arc4random_buf(out, len);
return 0;
#else
// Fallback: read from /dev/urandom
FILE *f = fopen("/dev/urandom", "rb");
if (!f)
return -1;
size_t n = fread(out, 1, len, f);
fclose(f);
return (n == len) ? 0 : -1;
#endif
}
} // namespace tinyaes

193
src/keyschedule.cpp Normal file
View File

@@ -0,0 +1,193 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "internal/aes_impl.h"
#include "internal/ghash.h"
#include "cpuid.h"
#include <atomic>
namespace tinyaes
{
namespace internal
{
// --- Encrypt block dispatch ---
static encrypt_block_fn resolve_encrypt_block();
static std::atomic<encrypt_block_fn> encrypt_block_impl {nullptr};
encrypt_block_fn get_encrypt_block()
{
auto fn = encrypt_block_impl.load(std::memory_order_acquire);
if (fn)
return fn;
fn = resolve_encrypt_block();
encrypt_block_impl.store(fn, std::memory_order_release);
return fn;
}
static encrypt_block_fn resolve_encrypt_block()
{
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(TINYAES_FORCE_PORTABLE)
auto features = detect_cpu_features();
if (features.aesni)
return aes_encrypt_block_aesni;
return aes_encrypt_block_portable;
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(TINYAES_FORCE_PORTABLE)
auto features = detect_cpu_features();
if (features.arm_aes)
return aes_encrypt_block_arm_ce;
return aes_encrypt_block_portable;
#else
return aes_encrypt_block_portable;
#endif
}
// --- Decrypt block dispatch ---
static decrypt_block_fn resolve_decrypt_block();
static std::atomic<decrypt_block_fn> decrypt_block_impl {nullptr};
decrypt_block_fn get_decrypt_block()
{
auto fn = decrypt_block_impl.load(std::memory_order_acquire);
if (fn)
return fn;
fn = resolve_decrypt_block();
decrypt_block_impl.store(fn, std::memory_order_release);
return fn;
}
static decrypt_block_fn resolve_decrypt_block()
{
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(TINYAES_FORCE_PORTABLE)
auto features = detect_cpu_features();
if (features.aesni)
return aes_decrypt_block_aesni;
return aes_decrypt_block_portable;
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(TINYAES_FORCE_PORTABLE)
auto features = detect_cpu_features();
if (features.arm_aes)
return aes_decrypt_block_arm_ce;
return aes_decrypt_block_portable;
#else
return aes_decrypt_block_portable;
#endif
}
// --- Key expand dispatch ---
static key_expand_fn resolve_key_expand();
static std::atomic<key_expand_fn> key_expand_impl {nullptr};
key_expand_fn get_key_expand()
{
auto fn = key_expand_impl.load(std::memory_order_acquire);
if (fn)
return fn;
fn = resolve_key_expand();
key_expand_impl.store(fn, std::memory_order_release);
return fn;
}
static key_expand_fn resolve_key_expand()
{
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(TINYAES_FORCE_PORTABLE)
auto features = detect_cpu_features();
if (features.aesni)
return aes_key_expand_aesni;
return aes_key_expand_portable;
#else
return aes_key_expand_portable;
#endif
}
// --- CTR pipeline dispatch ---
static ctr_pipeline_fn resolve_ctr_pipeline();
static std::atomic<ctr_pipeline_fn> ctr_pipeline_impl {nullptr};
ctr_pipeline_fn get_ctr_pipeline()
{
auto fn = ctr_pipeline_impl.load(std::memory_order_acquire);
if (fn)
return fn;
fn = resolve_ctr_pipeline();
ctr_pipeline_impl.store(fn, std::memory_order_release);
return fn;
}
static ctr_pipeline_fn resolve_ctr_pipeline()
{
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(TINYAES_FORCE_PORTABLE)
auto features = detect_cpu_features();
if (features.vaes && features.avx512f)
return aes_ctr_pipeline_vaes;
if (features.aesni)
return aes_ctr_pipeline_aesni;
return aes_ctr_pipeline_portable;
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(TINYAES_FORCE_PORTABLE)
auto features = detect_cpu_features();
if (features.arm_aes)
return aes_ctr_pipeline_arm_ce;
return aes_ctr_pipeline_portable;
#else
return aes_ctr_pipeline_portable;
#endif
}
// --- GHASH dispatch ---
static ghash_fn resolve_ghash();
static std::atomic<ghash_fn> ghash_impl {nullptr};
ghash_fn get_ghash()
{
auto fn = ghash_impl.load(std::memory_order_acquire);
if (fn)
return fn;
fn = resolve_ghash();
ghash_impl.store(fn, std::memory_order_release);
return fn;
}
static ghash_fn resolve_ghash()
{
#if (defined(__x86_64__) || defined(_M_X64)) && !defined(TINYAES_FORCE_PORTABLE)
auto features = detect_cpu_features();
if (features.vpclmulqdq && features.avx512f)
return ghash_vpclmulqdq;
if (features.pclmulqdq)
return ghash_pclmulqdq;
return ghash_portable;
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(TINYAES_FORCE_PORTABLE)
auto features = detect_cpu_features();
if (features.arm_pmull)
return ghash_arm_ce;
return ghash_portable;
#else
return ghash_portable;
#endif
}
} // namespace internal
} // namespace tinyaes

80
src/secure_zero.cpp Normal file
View File

@@ -0,0 +1,80 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "tinyaes/common.h"
#include <cstring>
#if defined(_WIN32)
#include <windows.h>
#endif
namespace tinyaes
{
#if defined(_WIN32)
void secure_zero(void *ptr, size_t len)
{
SecureZeroMemory(ptr, len);
}
#elif defined(__STDC_LIB_EXT1__)
void secure_zero(void *ptr, size_t len)
{
memset_s(ptr, len, 0, len);
}
#else
// Use a volatile function pointer to prevent dead-store elimination
static void *(*const volatile memset_ptr)(void *, int, size_t) = std::memset;
void secure_zero(void *ptr, size_t len)
{
memset_ptr(ptr, 0, len);
}
#endif
bool constant_time_equal(const uint8_t *a, const uint8_t *b, size_t len)
{
volatile uint8_t diff = 0;
for (size_t i = 0; i < len; ++i)
{
diff |= static_cast<uint8_t>(a[i] ^ b[i]);
}
volatile uint8_t result = static_cast<uint8_t>(diff == 0);
return result != 0;
}
} // namespace tinyaes
extern "C" int tinyaes_constant_time_equal(const uint8_t *a, const uint8_t *b, size_t len)
{
return tinyaes::constant_time_equal(a, b, len) ? 1 : 0;
}

43
tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,43 @@
add_executable(tinyaes_tests
test_main.cpp
test_keyschedule.cpp
test_ecb.cpp
test_cbc.cpp
test_padding.cpp
test_ctr.cpp
test_gcm.cpp
test_gcm_auth_failure.cpp
test_iv_generation.cpp
test_cpuid.cpp
)
target_link_libraries(tinyaes_tests PRIVATE tinyaes)
target_include_directories(tinyaes_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/../src
)
set_target_properties(tinyaes_tests PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
)
# Warning flags for tests
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(tinyaes_tests PRIVATE -Wall -Wextra -Wpedantic -Werror)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set_property(TARGET tinyaes_tests APPEND_STRING PROPERTY LINK_FLAGS
" -Wl,-z,relro,-z,now -Wl,-z,noexecstack")
endif()
if(MINGW)
set_property(TARGET tinyaes_tests APPEND_STRING PROPERTY LINK_FLAGS
" -Wl,--nxcompat -Wl,--dynamicbase -Wl,--high-entropy-va")
endif()
elseif(MSVC)
target_compile_options(tinyaes_tests PRIVATE /W4 /WX)
set_property(TARGET tinyaes_tests APPEND_STRING PROPERTY LINK_FLAGS
" /DYNAMICBASE /NXCOMPAT /HIGHENTROPYVA")
endif()
add_test(NAME tinyaes_tests COMMAND tinyaes_tests)

81
tests/test_cbc.cpp Normal file
View File

@@ -0,0 +1,81 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "test_harness.h"
#include "tinyaes/cbc.h"
#include "vectors/aes_cbc_vectors.inl"
#define VEC(arr) std::vector<uint8_t>(arr, arr + sizeof(arr))
TEST(cbc_aes128_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::cbc_encrypt(VEC(cbc_128_key), VEC(cbc_128_iv), VEC(cbc_128_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(cbc_128_cipher));
}
TEST(cbc_aes128_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::cbc_decrypt(VEC(cbc_128_key), VEC(cbc_128_iv), VEC(cbc_128_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(cbc_128_plain));
}
TEST(cbc_aes192_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::cbc_encrypt(VEC(cbc_192_key), VEC(cbc_192_iv), VEC(cbc_192_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(cbc_192_cipher));
}
TEST(cbc_aes192_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::cbc_decrypt(VEC(cbc_192_key), VEC(cbc_192_iv), VEC(cbc_192_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(cbc_192_plain));
}
TEST(cbc_aes256_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::cbc_encrypt(VEC(cbc_256_key), VEC(cbc_256_iv), VEC(cbc_256_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(cbc_256_cipher));
}
TEST(cbc_aes256_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::cbc_decrypt(VEC(cbc_256_key), VEC(cbc_256_iv), VEC(cbc_256_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(cbc_256_plain));
}
TEST(cbc_roundtrip_pkcs7)
{
std::vector<uint8_t> key(16, 0x42);
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f}; // "Hello"
std::vector<uint8_t> ct, pt;
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(ct.size() == 16); // 5 bytes + 11 padding = 16
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, plaintext);
}
TEST(cbc_invalid_iv_size)
{
std::vector<uint8_t> key(16, 0), iv(15, 0), pt(16, 0), ct;
ASSERT_TRUE(tinyaes::cbc_encrypt(key, iv, pt, ct) == tinyaes::Result::InvalidInput);
}
#undef VEC

39
tests/test_cpuid.cpp Normal file
View File

@@ -0,0 +1,39 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "test_harness.h"
#include "cpuid.h"
#include "internal/aes_impl.h"
TEST(cpuid_detect_no_crash)
{
// Just verify detection doesn't crash
auto features = tinyaes::internal::detect_cpu_features();
(void)features;
ASSERT_TRUE(true);
}
TEST(cpuid_dispatch_encrypt_block)
{
// Verify dispatch resolves to a non-null function pointer
auto fn = tinyaes::internal::get_encrypt_block();
ASSERT_TRUE(fn != nullptr);
}
TEST(cpuid_dispatch_decrypt_block)
{
auto fn = tinyaes::internal::get_decrypt_block();
ASSERT_TRUE(fn != nullptr);
}
TEST(cpuid_dispatch_key_expand)
{
auto fn = tinyaes::internal::get_key_expand();
ASSERT_TRUE(fn != nullptr);
}
TEST(cpuid_dispatch_ctr_pipeline)
{
auto fn = tinyaes::internal::get_ctr_pipeline();
ASSERT_TRUE(fn != nullptr);
}

77
tests/test_ctr.cpp Normal file
View File

@@ -0,0 +1,77 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "test_harness.h"
#include "tinyaes/ctr.h"
#include "vectors/aes_ctr_vectors.inl"
#define VEC(arr) std::vector<uint8_t>(arr, arr + sizeof(arr))
TEST(ctr_aes128_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_crypt(VEC(ctr_128_key), VEC(ctr_128_iv), VEC(ctr_128_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(ctr_128_cipher));
}
TEST(ctr_aes128_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::ctr_crypt(VEC(ctr_128_key), VEC(ctr_128_iv), VEC(ctr_128_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(ctr_128_plain));
}
TEST(ctr_aes192_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_crypt(VEC(ctr_192_key), VEC(ctr_192_iv), VEC(ctr_192_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(ctr_192_cipher));
}
TEST(ctr_aes256_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_crypt(VEC(ctr_256_key), VEC(ctr_256_iv), VEC(ctr_256_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(ctr_256_cipher));
}
TEST(ctr_partial_block)
{
// Encrypt 7 bytes with CTR — should handle partial block
std::vector<uint8_t> key(16, 0x42);
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
std::vector<uint8_t> ct, pt;
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(ct.size() == 7);
// Decrypt should recover original
result = tinyaes::ctr_crypt(key, iv, ct, pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, plaintext);
}
TEST(ctr_roundtrip_multi_block)
{
std::vector<uint8_t> key(32, 0xAB);
std::vector<uint8_t> iv(16, 0x01);
std::vector<uint8_t> plaintext(100, 0x55); // 6 full blocks + 4 remainder
std::vector<uint8_t> ct, pt;
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(ct.size() == 100);
result = tinyaes::ctr_crypt(key, iv, ct, pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, plaintext);
}
#undef VEC

93
tests/test_ecb.cpp Normal file
View File

@@ -0,0 +1,93 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "test_harness.h"
#include "tinyaes/ecb.h"
#include "vectors/aes_ecb_vectors.inl"
#define VEC(arr) std::vector<uint8_t>(arr, arr + sizeof(arr))
TEST(ecb_aes128_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ecb_encrypt(VEC(ecb_128_key), VEC(ecb_128_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(ecb_128_cipher));
}
TEST(ecb_aes128_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::ecb_decrypt(VEC(ecb_128_key), VEC(ecb_128_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(ecb_128_plain));
}
TEST(ecb_aes192_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ecb_encrypt(VEC(ecb_192_key), VEC(ecb_192_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(ecb_192_cipher));
}
TEST(ecb_aes192_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::ecb_decrypt(VEC(ecb_192_key), VEC(ecb_192_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(ecb_192_plain));
}
TEST(ecb_aes256_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ecb_encrypt(VEC(ecb_256_key), VEC(ecb_256_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(ecb_256_cipher));
}
TEST(ecb_aes256_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::ecb_decrypt(VEC(ecb_256_key), VEC(ecb_256_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(ecb_256_plain));
}
TEST(ecb_aes128_multi_block)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ecb_encrypt(VEC(ecb_128_multi_key), VEC(ecb_128_multi_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(ecb_128_multi_cipher));
}
TEST(ecb_aes128_multi_block_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::ecb_decrypt(VEC(ecb_128_multi_key), VEC(ecb_128_multi_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(ecb_128_multi_plain));
}
TEST(ecb_invalid_key_size)
{
std::vector<uint8_t> key(15, 0), pt(16, 0), ct;
ASSERT_TRUE(tinyaes::ecb_encrypt(key, pt, ct) == tinyaes::Result::InvalidKeySize);
}
TEST(ecb_non_block_aligned)
{
std::vector<uint8_t> key(16, 0), pt(17, 0), ct;
ASSERT_TRUE(tinyaes::ecb_encrypt(key, pt, ct) == tinyaes::Result::InvalidInput);
}
TEST(ecb_empty_input)
{
std::vector<uint8_t> key(16, 0), pt, ct;
ASSERT_TRUE(tinyaes::ecb_encrypt(key, pt, ct) == tinyaes::Result::InvalidInput);
}
#undef VEC

111
tests/test_gcm.cpp Normal file
View File

@@ -0,0 +1,111 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "test_harness.h"
#include "tinyaes/gcm.h"
#include "vectors/aes_gcm_vectors.inl"
#define VEC(arr) std::vector<uint8_t>(arr, arr + sizeof(arr))
#define EMPTY_VEC std::vector<uint8_t>()
TEST(gcm_tc1_aes128_no_plaintext_no_aad)
{
std::vector<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc1_key), VEC(gcm_tc1_iv), EMPTY_VEC, EMPTY_VEC, ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(ct.empty());
ASSERT_EQ(tag, VEC(gcm_tc1_tag));
}
TEST(gcm_tc2_aes128_16byte_plaintext)
{
std::vector<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc2_key), VEC(gcm_tc2_iv), EMPTY_VEC, VEC(gcm_tc2_plain), ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(gcm_tc2_cipher));
ASSERT_EQ(tag, VEC(gcm_tc2_tag));
}
TEST(gcm_tc3_aes128_64byte_plaintext)
{
std::vector<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc3_key), VEC(gcm_tc3_iv), EMPTY_VEC, VEC(gcm_tc3_plain), ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(gcm_tc3_cipher));
ASSERT_EQ(tag, VEC(gcm_tc3_tag));
}
TEST(gcm_tc4_aes128_with_aad)
{
std::vector<uint8_t> ct, tag;
auto result =
tinyaes::gcm_encrypt(VEC(gcm_tc4_key), VEC(gcm_tc4_iv), VEC(gcm_tc4_aad), VEC(gcm_tc4_plain), ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(gcm_tc4_cipher));
ASSERT_EQ(tag, VEC(gcm_tc4_tag));
}
TEST(gcm_tc13_aes256_no_plaintext_no_aad)
{
std::vector<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc13_key), VEC(gcm_tc13_iv), EMPTY_VEC, EMPTY_VEC, ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(ct.empty());
ASSERT_EQ(tag, VEC(gcm_tc13_tag));
}
TEST(gcm_tc14_aes256_16byte_plaintext)
{
std::vector<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc14_key), VEC(gcm_tc14_iv), EMPTY_VEC, VEC(gcm_tc14_plain), ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(ct, VEC(gcm_tc14_cipher));
ASSERT_EQ(tag, VEC(gcm_tc14_tag));
}
TEST(gcm_tc2_decrypt_verify)
{
std::vector<uint8_t> pt;
auto result =
tinyaes::gcm_decrypt(VEC(gcm_tc2_key), VEC(gcm_tc2_iv), EMPTY_VEC, VEC(gcm_tc2_cipher), VEC(gcm_tc2_tag), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(gcm_tc2_plain));
}
TEST(gcm_tc3_decrypt_verify)
{
std::vector<uint8_t> pt;
auto result =
tinyaes::gcm_decrypt(VEC(gcm_tc3_key), VEC(gcm_tc3_iv), EMPTY_VEC, VEC(gcm_tc3_cipher), VEC(gcm_tc3_tag), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(gcm_tc3_plain));
}
TEST(gcm_tc4_decrypt_verify)
{
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(VEC(gcm_tc4_key), VEC(gcm_tc4_iv), VEC(gcm_tc4_aad), VEC(gcm_tc4_cipher),
VEC(gcm_tc4_tag), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, VEC(gcm_tc4_plain));
}
TEST(gcm_roundtrip)
{
std::vector<uint8_t> key(16, 0x42);
std::vector<uint8_t> iv(12, 0x01);
std::vector<uint8_t> aad = {0xAA, 0xBB, 0xCC};
std::vector<uint8_t> plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f}; // "Hello"
std::vector<uint8_t> ct, tag, pt;
auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
result = tinyaes::gcm_decrypt(key, iv, aad, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, plaintext);
}
#undef VEC
#undef EMPTY_VEC

View File

@@ -0,0 +1,97 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "test_harness.h"
#include "tinyaes/gcm.h"
static std::vector<uint8_t> make_key()
{
return std::vector<uint8_t>(16, 0x42);
}
static std::vector<uint8_t> make_iv()
{
return std::vector<uint8_t>(12, 0x01);
}
// Helper: encrypt a known message
static void encrypt_helper(
std::vector<uint8_t> &ct,
std::vector<uint8_t> &tag,
const std::vector<uint8_t> &aad = {},
const std::vector<uint8_t> &pt = {0x48, 0x65, 0x6c, 0x6c, 0x6f})
{
auto key = make_key();
auto iv = make_iv();
tinyaes::gcm_encrypt(key, iv, aad, pt, ct, tag);
}
TEST(gcm_auth_fail_tampered_ciphertext)
{
std::vector<uint8_t> ct, tag;
encrypt_helper(ct, tag);
// Tamper with ciphertext
ct[0] ^= 0x01;
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), {}, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(pt.empty());
}
TEST(gcm_auth_fail_tampered_tag)
{
std::vector<uint8_t> ct, tag;
encrypt_helper(ct, tag);
// Tamper with tag
tag[0] ^= 0x01;
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), {}, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(pt.empty());
}
TEST(gcm_auth_fail_tampered_aad)
{
std::vector<uint8_t> aad = {0xAA, 0xBB, 0xCC};
std::vector<uint8_t> ct, tag;
encrypt_helper(ct, tag, aad);
// Tamper with AAD
std::vector<uint8_t> bad_aad = {0xAA, 0xBB, 0xCD};
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), bad_aad, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(pt.empty());
}
TEST(gcm_auth_fail_wrong_key)
{
std::vector<uint8_t> ct, tag;
encrypt_helper(ct, tag);
// Use wrong key
std::vector<uint8_t> wrong_key(16, 0x43);
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(wrong_key, make_iv(), {}, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(pt.empty());
}
TEST(gcm_auth_fail_wrong_iv)
{
std::vector<uint8_t> ct, tag;
encrypt_helper(ct, tag);
// Use wrong IV
std::vector<uint8_t> wrong_iv(12, 0x02);
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(make_key(), wrong_iv, {}, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(pt.empty());
}

125
tests/test_harness.h Normal file
View File

@@ -0,0 +1,125 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <string>
#include <vector>
namespace test
{
struct TestCase
{
std::string name;
std::function<void()> fn;
};
inline int &fail_count()
{
static int n = 0;
return n;
}
inline int &pass_count()
{
static int n = 0;
return n;
}
inline std::vector<TestCase> &test_registry()
{
static std::vector<TestCase> cases;
return cases;
}
struct TestRegistrar
{
TestRegistrar(const char *name, std::function<void()> fn)
{
test_registry().push_back({name, std::move(fn)});
}
};
inline void
assert_eq_bytes(const std::vector<uint8_t> &a, const std::vector<uint8_t> &b, const char *file, int line)
{
if (a.size() != b.size() || std::memcmp(a.data(), b.data(), a.size()) != 0)
{
std::fprintf(stderr, "%s:%d: ASSERT_EQ failed\n got: ", file, line);
for (auto x : a)
std::fprintf(stderr, "%02x", x);
std::fprintf(stderr, "\n expected: ");
for (auto x : b)
std::fprintf(stderr, "%02x", x);
std::fprintf(stderr, "\n");
fail_count()++;
}
else
{
pass_count()++;
}
}
inline void assert_true(bool cond, const char *expr, const char *file, int line)
{
if (!cond)
{
std::fprintf(stderr, "%s:%d: ASSERT_TRUE(%s) failed\n", file, line, expr);
fail_count()++;
}
else
{
pass_count()++;
}
}
inline int run_all()
{
for (auto &tc : test_registry())
{
std::printf(" RUN %s\n", tc.name.c_str());
tc.fn();
}
std::printf("\n%d passed, %d failed\n", pass_count(), fail_count());
return fail_count() > 0 ? 1 : 0;
}
} // namespace test
#define TEST(name) \
static void test_##name(); \
static test::TestRegistrar reg_##name(#name, test_##name); \
static void test_##name()
#define ASSERT_EQ(a, b) test::assert_eq_bytes((a), (b), __FILE__, __LINE__)
#define ASSERT_TRUE(cond) test::assert_true((cond), #cond, __FILE__, __LINE__)
#define RUN_ALL_TESTS() test::run_all()

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "test_harness.h"
#include "tinyaes/common.h"
#include <cstring>
TEST(iv_generate_12_bytes)
{
uint8_t iv[12] = {0};
int result = tinyaes::generate_iv(iv, 12);
ASSERT_TRUE(result == 0);
// Check that it's not all zeros (extremely unlikely for CSPRNG)
uint8_t zeros[12] = {0};
ASSERT_TRUE(std::memcmp(iv, zeros, 12) != 0);
}
TEST(iv_generate_16_bytes)
{
uint8_t iv[16] = {0};
int result = tinyaes::generate_iv(iv, 16);
ASSERT_TRUE(result == 0);
uint8_t zeros[16] = {0};
ASSERT_TRUE(std::memcmp(iv, zeros, 16) != 0);
}
TEST(iv_generate_uniqueness)
{
// Two consecutive IV generations should produce different values
uint8_t iv1[16], iv2[16];
tinyaes::generate_iv(iv1, 16);
tinyaes::generate_iv(iv2, 16);
ASSERT_TRUE(std::memcmp(iv1, iv2, 16) != 0);
}
TEST(iv_generate_null_rejected)
{
int result = tinyaes::generate_iv(nullptr, 16);
ASSERT_TRUE(result == -1);
}
TEST(iv_generate_zero_len_rejected)
{
uint8_t iv[1];
int result = tinyaes::generate_iv(iv, 0);
ASSERT_TRUE(result == -1);
}

116
tests/test_keyschedule.cpp Normal file
View File

@@ -0,0 +1,116 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "test_harness.h"
#include "internal/aes_impl.h"
#include "vectors/aes_keyschedule_vectors.inl"
// Test portable key expansion directly (always big-endian uint32_t format)
TEST(keyschedule_portable_aes128)
{
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS] = {0};
tinyaes::internal::aes_key_expand_portable(ks_128_key, 16, rk);
// Verify all 44 words in big-endian format
std::vector<uint8_t> got, expected;
for (int i = 0; i < 44; ++i)
{
uint8_t buf[4];
buf[0] = static_cast<uint8_t>(rk[i] >> 24);
buf[1] = static_cast<uint8_t>(rk[i] >> 16);
buf[2] = static_cast<uint8_t>(rk[i] >> 8);
buf[3] = static_cast<uint8_t>(rk[i]);
got.insert(got.end(), buf, buf + 4);
buf[0] = static_cast<uint8_t>(ks_128_expected[i] >> 24);
buf[1] = static_cast<uint8_t>(ks_128_expected[i] >> 16);
buf[2] = static_cast<uint8_t>(ks_128_expected[i] >> 8);
buf[3] = static_cast<uint8_t>(ks_128_expected[i]);
expected.insert(expected.end(), buf, buf + 4);
}
ASSERT_EQ(got, expected);
}
TEST(keyschedule_portable_aes192_first_words)
{
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS] = {0};
tinyaes::internal::aes_key_expand_portable(ks_192_key, 24, rk);
for (int i = 0; i < 4; ++i)
{
ASSERT_TRUE(rk[i] == ks_192_expected_first[i]);
}
}
TEST(keyschedule_portable_aes256_first_words)
{
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS] = {0};
tinyaes::internal::aes_key_expand_portable(ks_256_key, 32, rk);
for (int i = 0; i < 8; ++i)
{
ASSERT_TRUE(rk[i] == ks_256_expected_first[i]);
}
}
// Functional test: dispatched key expand + encrypt + decrypt roundtrip
TEST(keyschedule_dispatch_roundtrip_128)
{
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS] = {0};
auto key_expand = tinyaes::internal::get_key_expand();
auto encrypt_block = tinyaes::internal::get_encrypt_block();
auto decrypt_block = tinyaes::internal::get_decrypt_block();
key_expand(ks_128_key, 16, rk);
uint8_t plain[16] = {0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d,
0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34};
uint8_t cipher[16], recovered[16];
encrypt_block(rk, 10, plain, cipher);
decrypt_block(rk, 10, cipher, recovered);
std::vector<uint8_t> p(plain, plain + 16);
std::vector<uint8_t> r(recovered, recovered + 16);
ASSERT_EQ(p, r);
}
TEST(keyschedule_dispatch_roundtrip_192)
{
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS] = {0};
auto key_expand = tinyaes::internal::get_key_expand();
auto encrypt_block = tinyaes::internal::get_encrypt_block();
auto decrypt_block = tinyaes::internal::get_decrypt_block();
key_expand(ks_192_key, 24, rk);
uint8_t plain[16] = {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a};
uint8_t cipher[16], recovered[16];
encrypt_block(rk, 12, plain, cipher);
decrypt_block(rk, 12, cipher, recovered);
std::vector<uint8_t> p(plain, plain + 16);
std::vector<uint8_t> r(recovered, recovered + 16);
ASSERT_EQ(p, r);
}
TEST(keyschedule_dispatch_roundtrip_256)
{
uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS] = {0};
auto key_expand = tinyaes::internal::get_key_expand();
auto encrypt_block = tinyaes::internal::get_encrypt_block();
auto decrypt_block = tinyaes::internal::get_decrypt_block();
key_expand(ks_256_key, 32, rk);
uint8_t plain[16] = {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a};
uint8_t cipher[16], recovered[16];
encrypt_block(rk, 14, plain, cipher);
decrypt_block(rk, 14, cipher, recovered);
std::vector<uint8_t> p(plain, plain + 16);
std::vector<uint8_t> r(recovered, recovered + 16);
ASSERT_EQ(p, r);
}

32
tests/test_main.cpp Normal file
View File

@@ -0,0 +1,32 @@
// Copyright (c) 2025-2026, Brandon Lehmann
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "test_harness.h"
int main()
{
return RUN_ALL_TESTS();
}

90
tests/test_padding.cpp Normal file
View File

@@ -0,0 +1,90 @@
// Copyright (c) 2025-2026, Brandon Lehmann
// BSD 3-Clause License (see LICENSE)
#include "test_harness.h"
#include "tinyaes/cbc.h"
TEST(pkcs7_full_block_padding)
{
// When input is exactly block-aligned, a full padding block (16 bytes of 0x10) is added
std::vector<uint8_t> key(16, 0xAA);
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext(16, 0x42); // Exactly one block
std::vector<uint8_t> ct, pt;
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(ct.size() == 32); // 16 data + 16 padding
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, plaintext);
}
TEST(pkcs7_single_byte)
{
std::vector<uint8_t> key(16, 0xBB);
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext = {0xFF};
std::vector<uint8_t> ct, pt;
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(ct.size() == 16); // 1 + 15 padding
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, plaintext);
}
TEST(pkcs7_empty_plaintext)
{
std::vector<uint8_t> key(16, 0xCC);
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext; // Empty
std::vector<uint8_t> ct, pt;
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(ct.size() == 16); // Full block of padding (0x10)
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, plaintext);
}
TEST(pkcs7_15_byte_plaintext)
{
std::vector<uint8_t> key(16, 0xDD);
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext(15, 0x42);
std::vector<uint8_t> ct, pt;
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(ct.size() == 16); // 15 + 1 padding byte
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_EQ(pt, plaintext);
}
TEST(pkcs7_invalid_padding_rejected)
{
// Construct ciphertext that decrypts to invalid padding
std::vector<uint8_t> key(16, 0xEE);
std::vector<uint8_t> iv(16, 0x00);
// Encrypt known plaintext, then corrupt last byte of ciphertext
std::vector<uint8_t> plaintext(16, 0x42);
std::vector<uint8_t> ct, pt;
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
// Flip a bit in the last block (padding block)
ct.back() ^= 0x01;
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
ASSERT_TRUE(result == tinyaes::Result::InvalidPadding);
}

View File

@@ -0,0 +1,51 @@
// NIST SP 800-38A CBC test vectors
// F.2.1: AES-128 CBC Encrypt
static const uint8_t cbc_128_key[] = {
0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c
};
static const uint8_t cbc_128_iv[] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
};
static const uint8_t cbc_128_plain[] = {
0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a,
0xae,0x2d,0x8a,0x57,0x1e,0x03,0xac,0x9c,0x9e,0xb7,0x6f,0xac,0x45,0xaf,0x8e,0x51,
0x30,0xc8,0x1c,0x46,0xa3,0x5c,0xe4,0x11,0xe5,0xfb,0xc1,0x19,0x1a,0x0a,0x52,0xef,
0xf6,0x9f,0x24,0x45,0xdf,0x4f,0x9b,0x17,0xad,0x2b,0x41,0x7b,0xe6,0x6c,0x37,0x10
};
static const uint8_t cbc_128_cipher[] = {
0x76,0x49,0xab,0xac,0x81,0x19,0xb2,0x46,0xce,0xe9,0x8e,0x9b,0x12,0xe9,0x19,0x7d,
0x50,0x86,0xcb,0x9b,0x50,0x72,0x19,0xee,0x95,0xdb,0x11,0x3a,0x91,0x76,0x78,0xb2,
0x73,0xbe,0xd6,0xb8,0xe3,0xc1,0x74,0x3b,0x71,0x16,0xe6,0x9e,0x22,0x22,0x95,0x16,
0x3f,0xf1,0xca,0xa1,0x68,0x1f,0xac,0x09,0x12,0x0e,0xca,0x30,0x75,0x86,0xe1,0xa7
};
// F.2.3: AES-192 CBC Encrypt
static const uint8_t cbc_192_key[] = {
0x8e,0x73,0xb0,0xf7,0xda,0x0e,0x64,0x52,0xc8,0x10,0xf3,0x2b,0x80,0x90,0x79,0xe5,
0x62,0xf8,0xea,0xd2,0x52,0x2c,0x6b,0x7b
};
static const uint8_t cbc_192_iv[] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
};
static const uint8_t cbc_192_plain[] = {
0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a
};
static const uint8_t cbc_192_cipher[] = {
0x4f,0x02,0x1d,0xb2,0x43,0xbc,0x63,0x3d,0x71,0x78,0x18,0x3a,0x9f,0xa0,0x71,0xe8
};
// F.2.5: AES-256 CBC Encrypt
static const uint8_t cbc_256_key[] = {
0x60,0x3d,0xeb,0x10,0x15,0xca,0x71,0xbe,0x2b,0x73,0xae,0xf0,0x85,0x7d,0x77,0x81,
0x1f,0x35,0x2c,0x07,0x3b,0x61,0x08,0xd7,0x2d,0x98,0x10,0xa3,0x09,0x14,0xdf,0xf4
};
static const uint8_t cbc_256_iv[] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
};
static const uint8_t cbc_256_plain[] = {
0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a
};
static const uint8_t cbc_256_cipher[] = {
0xf5,0x8c,0x4c,0x04,0xd6,0xe5,0xf1,0xba,0x77,0x9e,0xab,0xfb,0x5f,0x7b,0xfb,0xd6
};

View File

@@ -0,0 +1,51 @@
// NIST SP 800-38A CTR test vectors
// F.5.1: AES-128 CTR Encrypt
static const uint8_t ctr_128_key[] = {
0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c
};
static const uint8_t ctr_128_iv[] = {
0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
};
static const uint8_t ctr_128_plain[] = {
0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a,
0xae,0x2d,0x8a,0x57,0x1e,0x03,0xac,0x9c,0x9e,0xb7,0x6f,0xac,0x45,0xaf,0x8e,0x51,
0x30,0xc8,0x1c,0x46,0xa3,0x5c,0xe4,0x11,0xe5,0xfb,0xc1,0x19,0x1a,0x0a,0x52,0xef,
0xf6,0x9f,0x24,0x45,0xdf,0x4f,0x9b,0x17,0xad,0x2b,0x41,0x7b,0xe6,0x6c,0x37,0x10
};
static const uint8_t ctr_128_cipher[] = {
0x87,0x4d,0x61,0x91,0xb6,0x20,0xe3,0x26,0x1b,0xef,0x68,0x64,0x99,0x0d,0xb6,0xce,
0x98,0x06,0xf6,0x6b,0x79,0x70,0xfd,0xff,0x86,0x17,0x18,0x7b,0xb9,0xff,0xfd,0xff,
0x5a,0xe4,0xdf,0x3e,0xdb,0xd5,0xd3,0x5e,0x5b,0x4f,0x09,0x02,0x0d,0xb0,0x3e,0xab,
0x1e,0x03,0x1d,0xda,0x2f,0xbe,0x03,0xd1,0x79,0x21,0x70,0xa0,0xf3,0x00,0x9c,0xee
};
// F.5.3: AES-192 CTR Encrypt
static const uint8_t ctr_192_key[] = {
0x8e,0x73,0xb0,0xf7,0xda,0x0e,0x64,0x52,0xc8,0x10,0xf3,0x2b,0x80,0x90,0x79,0xe5,
0x62,0xf8,0xea,0xd2,0x52,0x2c,0x6b,0x7b
};
static const uint8_t ctr_192_iv[] = {
0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
};
static const uint8_t ctr_192_plain[] = {
0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a
};
static const uint8_t ctr_192_cipher[] = {
0x1a,0xbc,0x93,0x24,0x17,0x52,0x1c,0xa2,0x4f,0x2b,0x04,0x59,0xfe,0x7e,0x6e,0x0b
};
// F.5.5: AES-256 CTR Encrypt
static const uint8_t ctr_256_key[] = {
0x60,0x3d,0xeb,0x10,0x15,0xca,0x71,0xbe,0x2b,0x73,0xae,0xf0,0x85,0x7d,0x77,0x81,
0x1f,0x35,0x2c,0x07,0x3b,0x61,0x08,0xd7,0x2d,0x98,0x10,0xa3,0x09,0x14,0xdf,0xf4
};
static const uint8_t ctr_256_iv[] = {
0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
};
static const uint8_t ctr_256_plain[] = {
0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a
};
static const uint8_t ctr_256_cipher[] = {
0x60,0x1e,0xc3,0x13,0x77,0x57,0x89,0xa5,0xb7,0xa7,0xf5,0x04,0xbb,0xf3,0xd2,0x28
};

View File

@@ -0,0 +1,58 @@
// NIST AESAVS / FIPS 197 ECB test vectors
// FIPS 197 Appendix B: AES-128 ECB
// Key: 2b7e151628aed2a6abf7158809cf4f3c
// Plain: 3243f6a8885a308d313198a2e0370734
// Cipher: 3925841d02dc09fbdc118597196a0b32
static const uint8_t ecb_128_key[] = {
0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c
};
static const uint8_t ecb_128_plain[] = {
0x32,0x43,0xf6,0xa8,0x88,0x5a,0x30,0x8d,0x31,0x31,0x98,0xa2,0xe0,0x37,0x07,0x34
};
static const uint8_t ecb_128_cipher[] = {
0x39,0x25,0x84,0x1d,0x02,0xdc,0x09,0xfb,0xdc,0x11,0x85,0x97,0x19,0x6a,0x0b,0x32
};
// NIST SP 800-38A F.1.3: AES-192 ECB Encrypt
// Key: 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b
static const uint8_t ecb_192_key[] = {
0x8e,0x73,0xb0,0xf7,0xda,0x0e,0x64,0x52,0xc8,0x10,0xf3,0x2b,0x80,0x90,0x79,0xe5,
0x62,0xf8,0xea,0xd2,0x52,0x2c,0x6b,0x7b
};
static const uint8_t ecb_192_plain[] = {
0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a
};
static const uint8_t ecb_192_cipher[] = {
0xbd,0x33,0x4f,0x1d,0x6e,0x45,0xf2,0x5f,0xf7,0x12,0xa2,0x14,0x57,0x1f,0xa5,0xcc
};
// NIST SP 800-38A F.1.5: AES-256 ECB Encrypt
// Key: 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4
static const uint8_t ecb_256_key[] = {
0x60,0x3d,0xeb,0x10,0x15,0xca,0x71,0xbe,0x2b,0x73,0xae,0xf0,0x85,0x7d,0x77,0x81,
0x1f,0x35,0x2c,0x07,0x3b,0x61,0x08,0xd7,0x2d,0x98,0x10,0xa3,0x09,0x14,0xdf,0xf4
};
static const uint8_t ecb_256_plain[] = {
0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a
};
static const uint8_t ecb_256_cipher[] = {
0xf3,0xee,0xd1,0xbd,0xb5,0xd2,0xa0,0x3c,0x06,0x4b,0x5a,0x7e,0x3d,0xb1,0x81,0xf8
};
// NIST SP 800-38A F.1.1: AES-128 ECB Multi-block
static const uint8_t ecb_128_multi_plain[] = {
0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a,
0xae,0x2d,0x8a,0x57,0x1e,0x03,0xac,0x9c,0x9e,0xb7,0x6f,0xac,0x45,0xaf,0x8e,0x51,
0x30,0xc8,0x1c,0x46,0xa3,0x5c,0xe4,0x11,0xe5,0xfb,0xc1,0x19,0x1a,0x0a,0x52,0xef,
0xf6,0x9f,0x24,0x45,0xdf,0x4f,0x9b,0x17,0xad,0x2b,0x41,0x7b,0xe6,0x6c,0x37,0x10
};
static const uint8_t ecb_128_multi_key[] = {
0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c
};
static const uint8_t ecb_128_multi_cipher[] = {
0x3a,0xd7,0x7b,0xb4,0x0d,0x7a,0x36,0x60,0xa8,0x9e,0xca,0xf3,0x24,0x66,0xef,0x97,
0xf5,0xd3,0xd5,0x85,0x03,0xb9,0x69,0x9d,0xe7,0x85,0x89,0x5a,0x96,0xfd,0xba,0xaf,
0x43,0xb1,0xcd,0x7f,0x59,0x8e,0xce,0x23,0x88,0x1b,0x00,0xe3,0xed,0x03,0x06,0x88,
0x7b,0x0c,0x78,0x5e,0x27,0xe8,0xad,0x3f,0x82,0x23,0x20,0x71,0x04,0x72,0x5d,0xd4
};

View File

@@ -0,0 +1,110 @@
// NIST SP 800-38D GCM test vectors
// Test Case 1: AES-128, no plaintext, no AAD
static const uint8_t gcm_tc1_key[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static const uint8_t gcm_tc1_iv[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
// No plaintext, no AAD, no ciphertext
static const uint8_t gcm_tc1_tag[] = {
0x58,0xe2,0xfc,0xce,0xfa,0x7e,0x30,0x61,0x36,0x7f,0x1d,0x57,0xa4,0xe7,0x45,0x5a
};
// Test Case 2: AES-128, 16 bytes plaintext, no AAD
static const uint8_t gcm_tc2_key[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static const uint8_t gcm_tc2_iv[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static const uint8_t gcm_tc2_plain[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static const uint8_t gcm_tc2_cipher[] = {
0x03,0x88,0xda,0xce,0x60,0xb6,0xa3,0x92,0xf3,0x28,0xc2,0xb9,0x71,0xb2,0xfe,0x78
};
static const uint8_t gcm_tc2_tag[] = {
0xab,0x6e,0x47,0xd4,0x2c,0xec,0x13,0xbd,0xf5,0x3a,0x67,0xb2,0x12,0x57,0xbd,0xdf
};
// Test Case 3: AES-128, 64 bytes plaintext, no AAD
static const uint8_t gcm_tc3_key[] = {
0xfe,0xff,0xe9,0x92,0x86,0x65,0x73,0x1c,0x6d,0x6a,0x8f,0x94,0x67,0x30,0x83,0x08
};
static const uint8_t gcm_tc3_iv[] = {
0xca,0xfe,0xba,0xbe,0xfa,0xce,0xdb,0xad,0xde,0xca,0xf8,0x88
};
static const uint8_t gcm_tc3_plain[] = {
0xd9,0x31,0x32,0x25,0xf8,0x84,0x06,0xe5,0xa5,0x59,0x09,0xc5,0xaf,0xf5,0x26,0x9a,
0x86,0xa7,0xa9,0x53,0x15,0x34,0xf7,0xda,0x2e,0x4c,0x30,0x3d,0x8a,0x31,0x8a,0x72,
0x1c,0x3c,0x0c,0x95,0x95,0x68,0x09,0x53,0x2f,0xcf,0x0e,0x24,0x49,0xa6,0xb5,0x25,
0xb1,0x6a,0xed,0xf5,0xaa,0x0d,0xe6,0x57,0xba,0x63,0x7b,0x39,0x1a,0xaf,0xd2,0x55
};
static const uint8_t gcm_tc3_cipher[] = {
0x42,0x83,0x1e,0xc2,0x21,0x77,0x74,0x24,0x4b,0x72,0x21,0xb7,0x84,0xd0,0xd4,0x9c,
0xe3,0xaa,0x21,0x2f,0x2c,0x02,0xa4,0xe0,0x35,0xc1,0x7e,0x23,0x29,0xac,0xa1,0x2e,
0x21,0xd5,0x14,0xb2,0x54,0x66,0x93,0x1c,0x7d,0x8f,0x6a,0x5a,0xac,0x84,0xaa,0x05,
0x1b,0xa3,0x0b,0x39,0x6a,0x0a,0xac,0x97,0x3d,0x58,0xe0,0x91,0x47,0x3f,0x59,0x85
};
static const uint8_t gcm_tc3_tag[] = {
0x4d,0x5c,0x2a,0xf3,0x27,0xcd,0x64,0xa6,0x2c,0xf3,0x5a,0xbd,0x2b,0xa6,0xfa,0xb4
};
// Test Case 4: AES-128 with AAD (60 bytes plaintext, 20 bytes AAD)
static const uint8_t gcm_tc4_key[] = {
0xfe,0xff,0xe9,0x92,0x86,0x65,0x73,0x1c,0x6d,0x6a,0x8f,0x94,0x67,0x30,0x83,0x08
};
static const uint8_t gcm_tc4_iv[] = {
0xca,0xfe,0xba,0xbe,0xfa,0xce,0xdb,0xad,0xde,0xca,0xf8,0x88
};
static const uint8_t gcm_tc4_aad[] = {
0xfe,0xed,0xfa,0xce,0xde,0xad,0xbe,0xef,0xfe,0xed,0xfa,0xce,0xde,0xad,0xbe,0xef,
0xab,0xad,0xda,0xd2
};
static const uint8_t gcm_tc4_plain[] = {
0xd9,0x31,0x32,0x25,0xf8,0x84,0x06,0xe5,0xa5,0x59,0x09,0xc5,0xaf,0xf5,0x26,0x9a,
0x86,0xa7,0xa9,0x53,0x15,0x34,0xf7,0xda,0x2e,0x4c,0x30,0x3d,0x8a,0x31,0x8a,0x72,
0x1c,0x3c,0x0c,0x95,0x95,0x68,0x09,0x53,0x2f,0xcf,0x0e,0x24,0x49,0xa6,0xb5,0x25,
0xb1,0x6a,0xed,0xf5,0xaa,0x0d,0xe6,0x57,0xba,0x63,0x7b,0x39
};
static const uint8_t gcm_tc4_cipher[] = {
0x42,0x83,0x1e,0xc2,0x21,0x77,0x74,0x24,0x4b,0x72,0x21,0xb7,0x84,0xd0,0xd4,0x9c,
0xe3,0xaa,0x21,0x2f,0x2c,0x02,0xa4,0xe0,0x35,0xc1,0x7e,0x23,0x29,0xac,0xa1,0x2e,
0x21,0xd5,0x14,0xb2,0x54,0x66,0x93,0x1c,0x7d,0x8f,0x6a,0x5a,0xac,0x84,0xaa,0x05,
0x1b,0xa3,0x0b,0x39,0x6a,0x0a,0xac,0x97,0x3d,0x58,0xe0,0x91
};
static const uint8_t gcm_tc4_tag[] = {
0x5b,0xc9,0x4f,0xbc,0x32,0x21,0xa5,0xdb,0x94,0xfa,0xe9,0x5a,0xe7,0x12,0x1a,0x47
};
// Test Case 13: AES-256, no plaintext, no AAD
static const uint8_t gcm_tc13_key[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static const uint8_t gcm_tc13_iv[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static const uint8_t gcm_tc13_tag[] = {
0x53,0x0f,0x8a,0xfb,0xc7,0x45,0x36,0xb9,0xa9,0x63,0xb4,0xf1,0xc4,0xcb,0x73,0x8b
};
// Test Case 14: AES-256, 16 bytes plaintext, no AAD
static const uint8_t gcm_tc14_key[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static const uint8_t gcm_tc14_iv[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static const uint8_t gcm_tc14_plain[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static const uint8_t gcm_tc14_cipher[] = {
0xce,0xa7,0x40,0x3d,0x4d,0x60,0x6b,0x6e,0x07,0x4e,0xc5,0xd3,0xba,0xf3,0x9d,0x18
};
static const uint8_t gcm_tc14_tag[] = {
0xd0,0xd1,0xc8,0xa7,0x99,0x99,0x6b,0xf0,0x26,0x5b,0x98,0xb5,0xd4,0x8a,0xb9,0x19
};

View File

@@ -0,0 +1,41 @@
// FIPS 197 Appendix A: Key Expansion test vectors
// A.1: AES-128 Key Expansion
static const uint8_t ks_128_key[] = {
0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c
};
// Expected round keys (44 words = 176 bytes)
static const uint32_t ks_128_expected[] = {
0x2b7e1516, 0x28aed2a6, 0xabf71588, 0x09cf4f3c,
0xa0fafe17, 0x88542cb1, 0x23a33939, 0x2a6c7605,
0xf2c295f2, 0x7a96b943, 0x5935807a, 0x7359f67f,
0x3d80477d, 0x4716fe3e, 0x1e237e44, 0x6d7a883b,
0xef44a541, 0xa8525b7f, 0xb671253b, 0xdb0bad00,
0xd4d1c6f8, 0x7c839d87, 0xcaf2b8bc, 0x11f915bc,
0x6d88a37a, 0x110b3efd, 0xdbf98641, 0xca0093fd,
0x4e54f70e, 0x5f5fc9f3, 0x84a64fb2, 0x4ea6dc4f,
0xead27321, 0xb58dbad2, 0x312bf560, 0x7f8d292f,
0xac7766f3, 0x19fadc21, 0x28d12941, 0x575c006e,
0xd014f9a8, 0xc9ee2589, 0xe13f0cc8, 0xb6630ca6
};
// A.2: AES-192 Key Expansion
static const uint8_t ks_192_key[] = {
0x8e,0x73,0xb0,0xf7,0xda,0x0e,0x64,0x52,0xc8,0x10,0xf3,0x2b,0x80,0x90,0x79,0xe5,
0x62,0xf8,0xea,0xd2,0x52,0x2c,0x6b,0x7b
};
// First 4 round key words
static const uint32_t ks_192_expected_first[] = {
0x8e73b0f7, 0xda0e6452, 0xc810f32b, 0x809079e5
};
// A.3: AES-256 Key Expansion
static const uint8_t ks_256_key[] = {
0x60,0x3d,0xeb,0x10,0x15,0xca,0x71,0xbe,0x2b,0x73,0xae,0xf0,0x85,0x7d,0x77,0x81,
0x1f,0x35,0x2c,0x07,0x3b,0x61,0x08,0xd7,0x2d,0x98,0x10,0xa3,0x09,0x14,0xdf,0xf4
};
// First 8 round key words
static const uint32_t ks_256_expected_first[] = {
0x603deb10, 0x15ca71be, 0x2b73aef0, 0x857d7781,
0x1f352c07, 0x3b6108d7, 0x2d9810a3, 0x0914dff4
};