initial commit
This commit is contained in:
213
CMakeLists.txt
Normal file
213
CMakeLists.txt
Normal 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
25
LICENSE
Normal 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
20
bench/CMakeLists.txt
Normal 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
85
bench/bench_all.cpp
Normal 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
24
fuzz/CMakeLists.txt
Normal 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
35
fuzz/fuzz_cbc.cpp
Normal 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
29
fuzz/fuzz_ctr.cpp
Normal 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
32
fuzz/fuzz_ecb.cpp
Normal 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
35
fuzz/fuzz_gcm.cpp
Normal 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
34
include/tinyaes.h
Normal 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
111
include/tinyaes/cbc.h
Normal 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
107
include/tinyaes/common.h
Normal 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
66
include/tinyaes/ctr.h
Normal 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
75
include/tinyaes/ecb.h
Normal 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
91
include/tinyaes/gcm.h
Normal 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
32
include/tinyaes/version.h
Normal 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
229
src/backend/aes_aesni.cpp
Normal 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
127
src/backend/aes_arm_ce.cpp
Normal 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
|
||||
338
src/backend/aes_portable.cpp
Normal file
338
src/backend/aes_portable.cpp
Normal 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
116
src/backend/aes_vaes.cpp
Normal 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
|
||||
114
src/backend/ghash_arm_ce.cpp
Normal file
114
src/backend/ghash_arm_ce.cpp
Normal 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
|
||||
128
src/backend/ghash_pclmulqdq.cpp
Normal file
128
src/backend/ghash_pclmulqdq.cpp
Normal 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
|
||||
113
src/backend/ghash_portable.cpp
Normal file
113
src/backend/ghash_portable.cpp
Normal 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
|
||||
49
src/backend/ghash_vpclmulqdq.cpp
Normal file
49
src/backend/ghash_vpclmulqdq.cpp
Normal 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
300
src/cbc.cpp
Normal 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
124
src/cpuid.cpp
Normal 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
52
src/cpuid.h
Normal 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
118
src/ctr.cpp
Normal 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
143
src/ecb.cpp
Normal 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
354
src/gcm.cpp
Normal 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
94
src/internal/aes_impl.h
Normal 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
79
src/internal/endian.h
Normal 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
57
src/internal/ghash.h
Normal 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
66
src/iv_generate.cpp
Normal 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
193
src/keyschedule.cpp
Normal 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
80
src/secure_zero.cpp
Normal 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
43
tests/CMakeLists.txt
Normal 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
81
tests/test_cbc.cpp
Normal 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
39
tests/test_cpuid.cpp
Normal 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
77
tests/test_ctr.cpp
Normal 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
93
tests/test_ecb.cpp
Normal 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
111
tests/test_gcm.cpp
Normal 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
|
||||
97
tests/test_gcm_auth_failure.cpp
Normal file
97
tests/test_gcm_auth_failure.cpp
Normal 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
125
tests/test_harness.h
Normal 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()
|
||||
50
tests/test_iv_generation.cpp
Normal file
50
tests/test_iv_generation.cpp
Normal 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
116
tests/test_keyschedule.cpp
Normal 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
32
tests/test_main.cpp
Normal 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
90
tests/test_padding.cpp
Normal 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);
|
||||
}
|
||||
51
tests/vectors/aes_cbc_vectors.inl
Normal file
51
tests/vectors/aes_cbc_vectors.inl
Normal 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
|
||||
};
|
||||
51
tests/vectors/aes_ctr_vectors.inl
Normal file
51
tests/vectors/aes_ctr_vectors.inl
Normal 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
|
||||
};
|
||||
58
tests/vectors/aes_ecb_vectors.inl
Normal file
58
tests/vectors/aes_ecb_vectors.inl
Normal 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
|
||||
};
|
||||
110
tests/vectors/aes_gcm_vectors.inl
Normal file
110
tests/vectors/aes_gcm_vectors.inl
Normal 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
|
||||
};
|
||||
41
tests/vectors/aes_keyschedule_vectors.inl
Normal file
41
tests/vectors/aes_keyschedule_vectors.inl
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user