From b4df5d078aa8a90cb92f7ccb8019370fc6ce71fd Mon Sep 17 00:00:00 2001 From: Brandon Lehmann Date: Tue, 24 Feb 2026 21:57:00 -0500 Subject: [PATCH] Fix ARM CE byte ordering, expand C/C++ API, and harden build ARM backends: fix round key byte-swap on little-endian (vrev32q_u8), rewrite decrypt to pre-process middle keys with InvMixColumns, fix GHASH PMULL reflect and reduction ordering. API: add nonce/IV-generating convenience overloads for CTR, CBC, and GCM (library generates and prepends nonce, appends tag). Add C API for IV/nonce generation. Rename error codes (TINYAES_OK, Result::Ok, Result::AuthenticationFailed, etc.). Build: add MinGW GCC AVX-512 debug alignment fix, harden bench/fuzz CMake targets (warnings-as-errors, linker hardening), align with tinysha CMake conventions. Add README. Tests: expand coverage for nonce-generating API overloads, add NIST GCM test vectors, improve fuzz target differential testing. --- CMakeLists.txt | 24 ++- README.md | 233 +++++++++++++++++++++ bench/CMakeLists.txt | 16 +- bench/bench_all.cpp | 327 +++++++++++++++++++++++++----- fuzz/CMakeLists.txt | 13 +- fuzz/fuzz_cbc.cpp | 46 ++++- fuzz/fuzz_ctr.cpp | 55 ++++- fuzz/fuzz_ecb.cpp | 55 ++++- fuzz/fuzz_gcm.cpp | 103 +++++++++- include/tinyaes/cbc.h | 12 ++ include/tinyaes/common.h | 34 +++- include/tinyaes/ctr.h | 48 ++++- include/tinyaes/gcm.h | 30 +++ src/backend/aes_arm_ce.cpp | 41 ++-- src/backend/ghash_arm_ce.cpp | 50 +++-- src/cbc.cpp | 112 +++++++--- src/ctr.cpp | 144 ++++++++++++- src/ecb.cpp | 29 +-- src/gcm.cpp | 131 +++++++++--- src/iv_generate.cpp | 26 +++ src/secure_zero.cpp | 4 +- tests/CMakeLists.txt | 1 + tests/test_cbc.cpp | 66 +++++- tests/test_ctr.cpp | 80 +++++++- tests/test_ecb.cpp | 20 +- tests/test_gcm.cpp | 138 ++++++++++++- tests/test_gcm_auth_failure.cpp | 10 +- tests/test_harness.h | 4 +- tests/test_padding.cpp | 40 +++- tests/vectors/aes_gcm_vectors.inl | 31 +++ 30 files changed, 1646 insertions(+), 277 deletions(-) create mode 100644 README.md diff --git a/CMakeLists.txt b/CMakeLists.txt index c3837e3..87301e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") -fno-strict-overflow ) # Stack protector: requires libssp or compiler-rt at link time. + # Probe with try_compile to catch Clang+MinGW toolchains that lack libssp. 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) @@ -106,7 +107,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") # -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) + # Stack clash protection (Linux only — not supported on macOS/Apple Clang) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_compile_options(tinyaes PRIVATE -fstack-clash-protection) endif() @@ -118,9 +119,10 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(tinyaes PRIVATE -fcf-protection=full) endif() endif() - # -Wstrict-aliasing=2 only for GCC + # -Wstrict-aliasing=2 only for GCC (Clang doesn't support the =2 level) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(tinyaes PRIVATE -Wstrict-aliasing=2) + # libstdc++ container assertions in Debug (bounds checks on operator[], etc.) target_compile_options(tinyaes PRIVATE $<$:-D_GLIBCXX_ASSERTIONS> ) @@ -131,10 +133,11 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") $<$>:-D_FORTIFY_SOURCE=2> ) endif() - # Linker hardening + # Linker hardening (use LINK_FLAGS for CMake 3.10 compat) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-z,relro,-z,now -Wl,-z,noexecstack") + # Reject undefined symbols in shared libs (catches missing TINYAES_EXPORT) if(BUILD_SHARED_LIBS) set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-z,defs") endif() @@ -158,6 +161,7 @@ elseif(MSVC) endif() set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS " /DYNAMICBASE /NXCOMPAT /HIGHENTROPYVA") + # CET shadow stack compatibility check_cxx_compiler_flag("/guard:ehcont" HAS_GUARD_EHCONT) if(HAS_GUARD_EHCONT) target_compile_options(tinyaes PRIVATE /guard:ehcont) @@ -166,16 +170,26 @@ elseif(MSVC) endif() # --- Per-backend SIMD compile flags --- +# MinGW GCC + Debug: Windows x64 ABI only guarantees 16-byte stack alignment, +# but YMM/ZMM registers need 32/64-byte alignment. At -O0, GCC spills AVX +# registers to the stack with aligned moves (vmovdqa), which SIGSEGV on the +# misaligned stack. Force -O1 for SIMD backends in Debug mode so GCC keeps +# values in registers. -mpreferred-stack-boundary=5 is blocked by the ABI. +set(_MINGW_SIMD_FIX "") +if(MINGW AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(_MINGW_SIMD_FIX " $<$:-O1>") +endif() + 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") + COMPILE_FLAGS "-mavx512f -mvaes -maes -msse4.1${_MINGW_SIMD_FIX}") 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") + COMPILE_FLAGS "-mavx512f -mvpclmulqdq -mpclmul -msse4.1${_MINGW_SIMD_FIX}") elseif(MSVC) # MSVC: AES-NI and PCLMULQDQ available without extra flags on x64 set_source_files_properties(src/backend/aes_vaes.cpp PROPERTIES diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b5abdf --- /dev/null +++ b/README.md @@ -0,0 +1,233 @@ +# tinyaes + +A zero-dependency C++17 [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) encryption library supporting AES-128, AES-192, and AES-256 in ECB, CBC, CTR, and GCM modes, with SIMD-accelerated backends and runtime CPU dispatch. + +AES (Advanced Encryption Standard) is the most widely deployed symmetric cipher, used in TLS, disk encryption, VPNs, and virtually every modern security protocol. This library provides the complete set of block cipher modes you need for real applications -- from raw ECB blocks to authenticated encryption with GCM -- with both a C API and a modern C++ API. On x86_64, AES-NI and VAES hardware instructions are selected at runtime; on ARM64, the Crypto Extensions backend is used. A portable T-table implementation is always available as a fallback. + +## Features + +### Encryption Modes + +- **ECB** -- Electronic Codebook. Block-aligned encrypt/decrypt. Suitable only for single-block operations or as a building block for other modes. +- **CBC** -- Cipher Block Chaining. With or without PKCS#7 padding. Convenience overloads that auto-generate and prepend the IV. +- **CTR** -- Counter mode. Arbitrary-length encrypt/decrypt (no padding needed). Convenience overloads that auto-generate and prepend the nonce. +- **GCM** -- Galois/Counter Mode. Authenticated encryption with associated data (AEAD). Convenience overloads that auto-generate and prepend the nonce and append the authentication tag. + +### Key Sizes + +- **AES-128** (16-byte key), **AES-192** (24-byte key), **AES-256** (32-byte key) + +### SIMD Backends + +- **AES-NI + SSE4.1** (x86_64) -- hardware AES round instructions for encrypt/decrypt blocks +- **VAES + AVX-512** (x86_64) -- vectorized AES for CTR pipeline acceleration +- **PCLMULQDQ** (x86_64) -- carry-less multiplication for GHASH (GCM) +- **VPCLMULQDQ + AVX-512** (x86_64) -- vectorized carry-less multiplication for GHASH +- **ARM Crypto Extensions** (aarch64) -- hardware AES and PMULL instructions +- **Portable T-table** -- always compiled, works everywhere + +All SIMD backends are selected at runtime via CPUID (x86_64) or platform detection (ARM64). No runtime initialization call is needed -- dispatch happens lazily on first use via atomic function pointers. + +### Security + +- **Secure key erasure** -- all expanded round key material is zeroed via `secure_zero()` after every API call +- **Constant-time tag verification** -- GCM authentication uses constant-time comparison to prevent timing side-channels +- **Decrypt-then-verify** -- GCM decrypts into an internal buffer, verifies the tag, and only copies to the output on success (or zeros and fails) +- **Constant-time PKCS#7 unpadding** -- scans the entire last block to prevent padding oracle attacks +- **IV/nonce generation** -- uses `BCryptGenRandom` (Windows), `/dev/urandom` (Linux/macOS), or `arc4random_buf` (BSD) +- **Cross-platform** -- MSVC, GCC, Clang, MinGW + +## Building + +Requires CMake 3.10+ and a C++17 compiler. No external dependencies. + +```bash +# Configure and build +cmake -S . -B build -DBUILD_TESTS=ON +cmake --build build --config Release -j + +# Run tests +./build/tinyaes_tests # Linux / macOS +./build/Release/tinyaes_tests # Windows (MSVC) +``` + +### CMake Options + +| Option | Default | Description | +|--------|---------|-------------| +| `BUILD_TESTS` | `OFF` | Build the unit tests (`tinyaes_tests`) | +| `BUILD_BENCHMARKS` | `OFF` | Build the benchmark tool (`tinyaes_benchmarks`) | +| `BUILD_SHARED_LIBS` | `OFF` | Build as a shared library instead of static | +| `FORCE_PORTABLE` | `OFF` | Disable all SIMD backends; use only the portable T-table implementation | +| `CMAKE_BUILD_TYPE` | `Release` | `Debug`, `Release`, or `RelWithDebInfo` | + +## Usage + +Include the master header to access all modes: + +```cpp +#include "tinyaes.h" +``` + +Or include individual headers for specific modes: + +```cpp +#include "tinyaes/gcm.h" +#include "tinyaes/ctr.h" +#include "tinyaes/cbc.h" +#include "tinyaes/ecb.h" +``` + +Link against the `tinyaes` library target in your CMake project: + +```cmake +add_subdirectory(tinyaes) +target_link_libraries(your_target tinyaes) +``` + +### Quick Examples + +**GCM authenticated encryption** (recommended for most use cases): + +```cpp +#include "tinyaes/gcm.h" + +// Encrypt — library generates nonce, output is nonce||ciphertext||tag +std::vector key(32, 0x42); // AES-256 +std::vector plaintext = { /* ... */ }; +std::vector aad = { /* associated data */ }; +std::vector output; + +auto result = tinyaes::gcm_encrypt(key, plaintext, aad, output); +// output = 12-byte nonce || ciphertext || 16-byte tag + +// Decrypt — nonce is first 12 bytes, tag is last 16 bytes +std::vector decrypted; +result = tinyaes::gcm_decrypt(key, output, aad, decrypted); +if (result != tinyaes::Result::Ok) { /* authentication failed */ } +``` + +**CTR mode encryption**: + +```cpp +#include "tinyaes/ctr.h" + +std::vector key(32, 0x42); +std::vector plaintext = { /* ... */ }; +std::vector output; + +// Encrypt — library generates nonce, prepended to output +auto result = tinyaes::ctr_encrypt(key, plaintext, output); +// output = 12-byte nonce || ciphertext + +// Decrypt — nonce is first 12 bytes of input +std::vector decrypted; +result = tinyaes::ctr_decrypt(key, output, decrypted); +``` + +**CBC with PKCS#7 padding**: + +```cpp +#include "tinyaes/cbc.h" + +std::vector key(16, 0x42); // AES-128 +std::vector plaintext = { /* ... */ }; +std::vector output; + +// Encrypt — library generates IV, prepended to output +auto result = tinyaes::cbc_encrypt_pkcs7(key, plaintext, output); +// output = 16-byte IV || padded ciphertext + +// Decrypt — IV is first 16 bytes of input +std::vector decrypted; +result = tinyaes::cbc_decrypt_pkcs7(key, output, decrypted); +``` + +### C API + +All modes are also available as C functions returning `int` (0 on success): + +```c +#include "tinyaes/gcm.h" + +uint8_t key[32] = { /* ... */ }; +uint8_t nonce[12] = { /* ... */ }; +uint8_t tag[16]; +uint8_t plaintext[64] = { /* ... */ }; +uint8_t ciphertext[64]; + +int rc = tinyaes_gcm_encrypt(key, 32, nonce, 12, NULL, 0, + plaintext, 64, ciphertext, 64, tag); +``` + +Error codes are defined in `tinyaes/common.h`: + +| Code | Constant | Meaning | +|------|----------|---------| +| 0 | `TINYAES_OK` | Success | +| -1 | `TINYAES_INVALID_KEY_SIZE` | Key must be 16, 24, or 32 bytes | +| -2 | `TINYAES_INVALID_IV_SIZE` | IV must be 16 bytes | +| -3 | `TINYAES_INVALID_NONCE_SIZE` | Nonce must be 12 bytes | +| -4 | `TINYAES_INVALID_INPUT_SIZE` | Input not block-aligned (ECB/CBC without padding) | +| -5 | `TINYAES_INVALID_PADDING` | PKCS#7 padding is invalid | +| -6 | `TINYAES_AUTH_FAILED` | GCM authentication tag mismatch | +| -7 | `TINYAES_INTERNAL_ERROR` | Internal error (e.g. IV generation failure) | + +## Architecture + +### AES Core + +The AES implementation uses big-endian round key and state representation throughout, matching the FIPS 197 specification. Key expansion produces the full round key schedule once per API call and securely zeroes it before returning. + +Three AES backends are available: + +| Backend | Platform | Instructions | Description | +|---------|----------|-------------|-------------| +| **Portable** | All | None | T-table implementation using Te0-Te3 (encrypt) and Td0-Td3 (decrypt) lookup tables | +| **AES-NI** | x86_64 | `aesenc`, `aesenclast`, `aesdec`, `aesdeclast` | Hardware AES round instructions via SSE intrinsics | +| **VAES** | x86_64 | `vaesenc`, `vaesenclast` + AVX-512 | Vectorized AES for multi-block CTR pipeline | +| **ARM CE** | aarch64 | `AESE`, `AESMC`, `AESD`, `AESIMC` | ARM Crypto Extensions for hardware AES | + +### GHASH (GCM) + +GCM's universal hash function uses GF(2^128) multiplication with MSB-first bit ordering and the reduction polynomial 0xE1000...0. + +| Backend | Platform | Instructions | Description | +|---------|----------|-------------|-------------| +| **Portable** | All | None | Bit-by-bit constant-time multiplication | +| **PCLMULQDQ** | x86_64 | `pclmulqdq` | Carry-less multiplication with Karatsuba reduction | +| **VPCLMULQDQ** | x86_64 | `vpclmulqdq` + AVX-512 | Vectorized carry-less multiplication | +| **ARM PMULL** | aarch64 | `PMULL`, `PMULL2` | Polynomial multiplication via NEON | + +### Runtime Dispatch + +Each function pointer is stored in a `std::atomic` with acquire/release semantics. On first call, CPUID selects the best available backend and stores the function pointer. No mutexes, no `std::call_once`. Redundant resolution under contention is safe by design -- all backends produce identical results. + +SIMD compile flags (`-maes`, `-mpclmul`, `-mavx512f`, etc.) are applied only to individual backend source files via `set_source_files_properties` in CMake, never globally. The portable backend always compiles on all platforms. + +## Testing + +Build with `-DBUILD_TESTS=ON` to get the `tinyaes_tests` executable. The test suite uses a custom test harness (`test_harness.h`) with `ASSERT_EQ`/`TEST` macros -- no external test framework required. + +Test coverage includes: + +- **Key schedule** -- AES-128/192/256 key expansion against FIPS 197 test vectors +- **ECB** -- NIST test vectors for all three key sizes, encrypt and decrypt +- **CBC** -- encrypt/decrypt with known vectors, PKCS#7 padding/unpadding, block alignment validation +- **CTR** -- NIST test vectors, arbitrary-length encryption, nonce-based convenience API +- **GCM** -- NIST test vectors (multiple key sizes, AAD lengths, plaintext lengths), authentication tag verification, authentication failure rejection +- **PKCS#7 padding** -- correct padding for all block alignments (1-16 bytes), constant-time unpadding validation +- **IV/nonce generation** -- uniqueness and length verification via OS CSPRNG +- **CPUID** -- backend detection and dispatch verification + +### Fuzz Testing + +Fuzz targets for all four modes (ECB, CBC, CTR, GCM) are included in the `fuzz/` directory. They use [libFuzzer](https://llvm.org/docs/LibFuzzer.html) with AddressSanitizer and are built automatically on Linux with Clang. + +## Benchmarking + +Build with `-DBUILD_BENCHMARKS=ON` to get the `tinyaes_benchmarks` executable. Measures throughput (MB/s) and cycles/byte for all modes across all key sizes, with both dispatched (best available) and portable backends. + +## License + +This project is licensed under the [BSD 3-Clause License](LICENSE). diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt index f8d5325..4cc06ff 100644 --- a/bench/CMakeLists.txt +++ b/bench/CMakeLists.txt @@ -13,8 +13,20 @@ set_target_properties(tinyaes_benchmarks PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} ) +# Warning flags for benchmarks if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - target_compile_options(tinyaes_benchmarks PRIVATE -Wall -Wextra -Wpedantic) + target_compile_options(tinyaes_benchmarks PRIVATE -Wall -Wextra -Wpedantic -Werror) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set_property(TARGET tinyaes_benchmarks APPEND_STRING PROPERTY LINK_FLAGS + " -Wl,-z,relro,-z,now -Wl,-z,noexecstack") + endif() + # macOS: -bind_at_load is deprecated on modern macOS (eager binding is the default) + if(MINGW) + set_property(TARGET tinyaes_benchmarks APPEND_STRING PROPERTY LINK_FLAGS + " -Wl,--nxcompat -Wl,--dynamicbase -Wl,--high-entropy-va") + endif() elseif(MSVC) - target_compile_options(tinyaes_benchmarks PRIVATE /W4) + target_compile_options(tinyaes_benchmarks PRIVATE /W4 /WX) + set_property(TARGET tinyaes_benchmarks APPEND_STRING PROPERTY LINK_FLAGS + " /DYNAMICBASE /NXCOMPAT /HIGHENTROPYVA") endif() diff --git a/bench/bench_all.cpp b/bench/bench_all.cpp index 1c3788c..6ad758b 100644 --- a/bench/bench_all.cpp +++ b/bench/bench_all.cpp @@ -2,84 +2,307 @@ // BSD 3-Clause License (see LICENSE) #include "tinyaes.h" +#include "internal/aes_impl.h" +#include "internal/ghash.h" #include #include #include +#include #include -static constexpr size_t BENCH_SIZE = 1024 * 1024; // 1 MiB +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) +#define HAS_RDTSC 1 +#if defined(_MSC_VER) +#include +static inline uint64_t rdtsc() +{ + return __rdtsc(); +} +#else +static inline uint64_t rdtsc() +{ + uint32_t lo, hi; + __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); + return (static_cast(hi) << 32) | lo; +} +#endif +#else +#define HAS_RDTSC 0 +#endif + static constexpr int ITERATIONS = 100; -template static double bench(const char *name, Fn &&fn) +struct BenchResult +{ + double mib_per_sec; + double cycles_per_byte; // 0 if rdtsc not available +}; + +template static BenchResult bench(const char *name, size_t data_size, Fn &&fn) { // Warmup fn(); +#if HAS_RDTSC + uint64_t tsc_start = rdtsc(); +#endif auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < ITERATIONS; ++i) { fn(); } auto end = std::chrono::high_resolution_clock::now(); +#if HAS_RDTSC + uint64_t tsc_end = rdtsc(); +#endif + double ms = std::chrono::duration(end - start).count(); - double total_bytes = static_cast(BENCH_SIZE) * ITERATIONS; + double total_bytes = static_cast(data_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; + + double cpb = 0.0; +#if HAS_RDTSC + if (total_bytes > 0) + cpb = static_cast(tsc_end - tsc_start) / total_bytes; +#endif + + if (cpb > 0) + std::printf(" %-40s %8.2f MiB/s %6.2f c/B (%6.2f ms)\n", name, mib_per_sec, cpb, ms); + else + std::printf(" %-40s %8.2f MiB/s (%6.2f ms)\n", name, mib_per_sec, ms); + + return {mib_per_sec, cpb}; +} + +// Bench a single AES encrypt_block function at low level +template +static void bench_block_fn(const char *name, tinyaes::internal::key_expand_fn key_expand, EncFn enc_fn, + const uint8_t *key, size_t key_len) +{ + int rounds = tinyaes::internal::aes_rounds(key_len); + uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS]; + key_expand(key, key_len, rk); + + uint8_t block[16] = {0}; + bench( + name, 16, + [&]() + { + for (int i = 0; i < 1000; ++i) + enc_fn(rk, rounds, block, block); + }); +} + +static void run_mode_benchmarks() +{ + static const size_t sizes[] = {16, 256, 1024, 4096, 16384, 65536, 262144, 1048576}; + static const size_t key_lens[] = {16, 24, 32}; + static const char *key_names[] = {"128", "192", "256"}; + + for (size_t ki = 0; ki < 3; ++ki) + { + size_t kl = key_lens[ki]; + std::vector key(kl, 0x42); + std::vector iv_16(16, 0x01); + std::vector iv_12(12, 0x01); + std::vector aad = {0xAA, 0xBB, 0xCC, 0xDD}; + + std::printf("\n=== AES-%s ===\n", key_names[ki]); + + for (size_t sz : sizes) + { + // ECB needs block-aligned + size_t ecb_sz = (sz / 16) * 16; + if (ecb_sz == 0) + ecb_sz = 16; + std::vector pt_ecb(ecb_sz, 0x55); + std::vector pt(sz, 0x55); + + char label[128]; + + std::printf("\n--- %zu bytes ---\n", sz); + + std::snprintf(label, sizeof(label), "ECB-%s encrypt", key_names[ki]); + bench( + label, ecb_sz, + [&]() + { + std::vector ct; + tinyaes::ecb_encrypt(key, pt_ecb, ct); + }); + + std::snprintf(label, sizeof(label), "CBC-%s encrypt", key_names[ki]); + bench( + label, ecb_sz, + [&]() + { + std::vector ct; + tinyaes::cbc_encrypt(key, iv_16, pt_ecb, ct); + }); + + std::snprintf(label, sizeof(label), "CTR-%s encrypt", key_names[ki]); + bench( + label, sz, + [&]() + { + std::vector ct; + tinyaes::ctr_crypt(key, iv_16, pt, ct); + }); + + std::snprintf(label, sizeof(label), "GCM-%s encrypt", key_names[ki]); + bench( + label, sz, + [&]() + { + std::vector ct, tag; + tinyaes::gcm_encrypt(key, iv_12, aad, pt, ct, tag); + }); + + std::snprintf(label, sizeof(label), "GCM-%s decrypt", key_names[ki]); + // Pre-encrypt for decrypt bench + std::vector ct_pre, tag_pre; + tinyaes::gcm_encrypt(key, iv_12, aad, pt, ct_pre, tag_pre); + bench( + label, sz, + [&]() + { + std::vector dec; + tinyaes::gcm_decrypt(key, iv_12, aad, ct_pre, tag_pre, dec); + }); + } + } +} + +static void run_gcm_aad_benchmarks() +{ + std::printf("\n=== GCM AAD Variation (AES-128, 4096B plaintext) ===\n"); + + std::vector key(16, 0x42); + std::vector iv(12, 0x01); + std::vector pt(4096, 0x55); + + static const size_t aad_sizes[] = {0, 16, 64, 256, 1024, 4096}; + for (size_t aad_sz : aad_sizes) + { + std::vector aad(aad_sz, 0xAA); + char label[128]; + std::snprintf(label, sizeof(label), "GCM-128 AAD=%zuB", aad_sz); + bench( + label, 4096 + aad_sz, + [&]() + { + std::vector ct, tag; + tinyaes::gcm_encrypt(key, iv, aad, pt, ct, tag); + }); + } +} + +static void run_key_expansion_benchmarks() +{ + std::printf("\n=== Key Expansion ===\n"); + + static const size_t key_lens[] = {16, 24, 32}; + static const char *key_names[] = {"128", "192", "256"}; + + auto key_expand = tinyaes::internal::get_key_expand(); + + for (size_t ki = 0; ki < 3; ++ki) + { + std::vector key(key_lens[ki], 0x42); + uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS]; + + char label[128]; + std::snprintf(label, sizeof(label), "Key expand AES-%s (dispatched)", key_names[ki]); + bench( + label, key_lens[ki], + [&]() + { + for (int i = 0; i < 1000; ++i) + key_expand(key.data(), key.size(), rk); + }); + + std::snprintf(label, sizeof(label), "Key expand AES-%s (portable)", key_names[ki]); + bench( + label, key_lens[ki], + [&]() + { + for (int i = 0; i < 1000; ++i) + tinyaes::internal::aes_key_expand_portable(key.data(), key.size(), rk); + }); + } +} + +static void run_ghash_benchmarks() +{ + std::printf("\n=== GHASH ===\n"); + + // Compute H from a fixed key + uint8_t key_raw[16] = {0x42}; + uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS]; + tinyaes::internal::aes_key_expand_portable(key_raw, 16, rk); + uint8_t H[16] = {0}; + uint8_t zero[16] = {0}; + tinyaes::internal::aes_encrypt_block_portable(rk, 10, zero, H); + + auto ghash_dispatch = tinyaes::internal::get_ghash(); + + static const size_t sizes[] = {16, 256, 1024, 4096, 65536}; + for (size_t sz : sizes) + { + std::vector data(sz, 0x55); + + char label[128]; + std::snprintf(label, sizeof(label), "GHASH %zuB (dispatched)", sz); + bench( + label, sz, + [&]() + { + uint8_t Y[16] = {0}; + ghash_dispatch(H, data.data(), data.size(), Y); + }); + + std::snprintf(label, sizeof(label), "GHASH %zuB (portable)", sz); + bench( + label, sz, + [&]() + { + uint8_t Y[16] = {0}; + tinyaes::internal::ghash_portable(H, data.data(), data.size(), Y); + }); + } +} + +static void run_backend_comparison() +{ + std::printf("\n=== Backend Comparison (single block encrypt x1000) ===\n"); + + uint8_t key128[16] = {0x42}; + uint8_t key256[32] = {0x42}; + + // Portable + bench_block_fn("AES-128 portable", tinyaes::internal::aes_key_expand_portable, + tinyaes::internal::aes_encrypt_block_portable, key128, 16); + bench_block_fn("AES-256 portable", tinyaes::internal::aes_key_expand_portable, + tinyaes::internal::aes_encrypt_block_portable, key256, 32); + + // Dispatched (may be AES-NI, ARM CE, or portable) + bench_block_fn("AES-128 dispatched", tinyaes::internal::get_key_expand(), tinyaes::internal::get_encrypt_block(), + key128, 16); + bench_block_fn("AES-256 dispatched", tinyaes::internal::get_key_expand(), tinyaes::internal::get_encrypt_block(), + key256, 32); } int main() { - std::vector key_128(16, 0x42); - std::vector key_256(32, 0x42); - std::vector iv_16(16, 0x01); - std::vector iv_12(12, 0x01); - std::vector plaintext(BENCH_SIZE, 0x55); - std::vector aad = {0xAA, 0xBB, 0xCC, 0xDD}; - - std::printf("TinyAES Benchmarks (%zu bytes x %d iterations)\n", BENCH_SIZE, ITERATIONS); + std::printf("TinyAES Benchmarks (%d iterations per measurement)\n", ITERATIONS); std::printf("================================================================\n"); - // ECB - bench("ECB-128 encrypt", [&]() { - std::vector ct; - tinyaes::ecb_encrypt(key_128, plaintext, ct); - }); - bench("ECB-256 encrypt", [&]() { - std::vector ct; - tinyaes::ecb_encrypt(key_256, plaintext, ct); - }); - - // CBC - bench("CBC-128 encrypt", [&]() { - std::vector ct; - tinyaes::cbc_encrypt(key_128, iv_16, plaintext, ct); - }); - bench("CBC-256 encrypt", [&]() { - std::vector ct; - tinyaes::cbc_encrypt(key_256, iv_16, plaintext, ct); - }); - - // CTR - bench("CTR-128 encrypt", [&]() { - std::vector ct; - tinyaes::ctr_crypt(key_128, iv_16, plaintext, ct); - }); - bench("CTR-256 encrypt", [&]() { - std::vector ct; - tinyaes::ctr_crypt(key_256, iv_16, plaintext, ct); - }); - - // GCM - bench("GCM-128 encrypt", [&]() { - std::vector ct, tag; - tinyaes::gcm_encrypt(key_128, iv_12, aad, plaintext, ct, tag); - }); - bench("GCM-256 encrypt", [&]() { - std::vector ct, tag; - tinyaes::gcm_encrypt(key_256, iv_12, aad, plaintext, ct, tag); - }); + run_backend_comparison(); + run_key_expansion_benchmarks(); + run_ghash_benchmarks(); + run_mode_benchmarks(); + run_gcm_aad_benchmarks(); return 0; } diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt index 576f69a..7784476 100644 --- a/fuzz/CMakeLists.txt +++ b/fuzz/CMakeLists.txt @@ -1,6 +1,10 @@ if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") return() endif() + +# libFuzzer is only reliably available on Linux Clang. +# macOS: Apple Clang lacks the runtime; Homebrew LLVM has ABI mismatches +# with macOS system libc++. Windows: CRT mismatches in Debug/Release. if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") return() endif() @@ -19,6 +23,13 @@ foreach(target ${FUZZ_TARGETS}) CXX_EXTENSIONS OFF RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/fuzz ) - target_compile_options(${target} PRIVATE -fsanitize=fuzzer,address) + target_compile_options(${target} PRIVATE + -Wall -Wextra -Wpedantic -Werror + -fsanitize=fuzzer,address + ) set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " -fsanitize=fuzzer,address") + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS + " -Wl,-z,relro,-z,now -Wl,-z,noexecstack") + endif() endforeach() diff --git a/fuzz/fuzz_cbc.cpp b/fuzz/fuzz_cbc.cpp index 5f0bc87..d53a254 100644 --- a/fuzz/fuzz_cbc.cpp +++ b/fuzz/fuzz_cbc.cpp @@ -2,34 +2,62 @@ // BSD 3-Clause License (see LICENSE) #include "tinyaes/cbc.h" +#include "internal/aes_impl.h" #include #include #include +#include + +// Differential: verify portable encrypt_block matches dispatched for each block +static void diff_encrypt_block(const uint8_t *key, size_t key_len, const uint8_t block[16]) +{ + int rounds = tinyaes::internal::aes_rounds(key_len); + if (rounds == 0) + return; + + uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS]; + tinyaes::internal::aes_key_expand_portable(key, key_len, rk); + + uint8_t out_portable[16], out_dispatch[16]; + tinyaes::internal::aes_encrypt_block_portable(rk, rounds, block, out_portable); + tinyaes::internal::get_encrypt_block()(rk, rounds, block, out_dispatch); + assert(std::memcmp(out_portable, out_dispatch, 16) == 0); +} extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - if (size < 32) + if (size < 2) return 0; - // First 16 bytes = key, next 16 bytes = IV, rest = plaintext - std::vector key(data, data + 16); - std::vector iv(data + 16, data + 32); + // First byte selects key size: 16, 24, or 32 + static const size_t key_sizes[] = {16, 24, 32}; + size_t key_len = key_sizes[data[0] % 3]; + data++; + size--; - size_t remaining = size - 32; + if (size < key_len + 16 + 1) + return 0; + + std::vector key(data, data + key_len); + std::vector iv(data + key_len, data + key_len + 16); + + size_t remaining = size - key_len - 16; if (remaining == 0) return 0; - // Use PKCS#7 which accepts any length - std::vector plaintext(data + 32, data + size); + std::vector plaintext(data + key_len + 16, data + size); std::vector ct, pt; auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return 0; result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt); - assert(result == tinyaes::Result::Success); + assert(result == tinyaes::Result::Ok); assert(pt == plaintext); + // Differential: compare portable vs dispatched on a single block from the input + diff_encrypt_block(data, key_len, data + key_len); + return 0; } diff --git a/fuzz/fuzz_ctr.cpp b/fuzz/fuzz_ctr.cpp index 279555d..7c2b322 100644 --- a/fuzz/fuzz_ctr.cpp +++ b/fuzz/fuzz_ctr.cpp @@ -2,28 +2,69 @@ // BSD 3-Clause License (see LICENSE) #include "tinyaes/ctr.h" +#include "internal/aes_impl.h" #include #include #include +#include + +// Differential: run CTR pipeline via portable vs dispatched and compare +static void diff_ctr_pipeline(const uint8_t *key, size_t key_len, const uint8_t *data, size_t data_len, + const uint8_t iv[16]) +{ + int rounds = tinyaes::internal::aes_rounds(key_len); + if (rounds == 0) + return; + + size_t blocks = data_len / 16; + if (blocks == 0) + return; + + uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS]; + tinyaes::internal::aes_key_expand_portable(key, key_len, rk); + + std::vector out_portable(blocks * 16); + std::vector out_dispatch(blocks * 16); + uint8_t ctr_p[16], ctr_d[16]; + std::memcpy(ctr_p, iv, 16); + std::memcpy(ctr_d, iv, 16); + + tinyaes::internal::aes_ctr_pipeline_portable(rk, rounds, data, out_portable.data(), blocks, ctr_p); + tinyaes::internal::get_ctr_pipeline()(rk, rounds, data, out_dispatch.data(), blocks, ctr_d); + + assert(out_portable == out_dispatch); + assert(std::memcmp(ctr_p, ctr_d, 16) == 0); +} extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - if (size < 33) + if (size < 2) return 0; - // First 16 bytes = key, next 16 bytes = IV, rest = plaintext - std::vector key(data, data + 16); - std::vector iv(data + 16, data + 32); - std::vector plaintext(data + 32, data + size); + // First byte selects key size: 16, 24, or 32 + static const size_t key_sizes[] = {16, 24, 32}; + size_t key_len = key_sizes[data[0] % 3]; + data++; + size--; + + if (size < key_len + 16 + 1) + return 0; + + std::vector key(data, data + key_len); + std::vector iv(data + key_len, data + key_len + 16); + std::vector plaintext(data + key_len + 16, data + size); std::vector ct, pt; auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return 0; result = tinyaes::ctr_crypt(key, iv, ct, pt); - assert(result == tinyaes::Result::Success); + assert(result == tinyaes::Result::Ok); assert(pt == plaintext); + // Differential: portable vs dispatched CTR pipeline + diff_ctr_pipeline(data, key_len, data + key_len + 16, size - key_len - 16, data + key_len); + return 0; } diff --git a/fuzz/fuzz_ecb.cpp b/fuzz/fuzz_ecb.cpp index 7ea5213..3e60a5b 100644 --- a/fuzz/fuzz_ecb.cpp +++ b/fuzz/fuzz_ecb.cpp @@ -2,31 +2,72 @@ // BSD 3-Clause License (see LICENSE) #include "tinyaes/ecb.h" +#include "internal/aes_impl.h" #include #include #include +#include + +// Differential: compare dispatched encrypt_block against portable +static void diff_encrypt_block(const uint8_t *key, size_t key_len, const uint8_t *plaintext, size_t pt_len) +{ + int rounds = tinyaes::internal::aes_rounds(key_len); + if (rounds == 0) + return; + + uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS]; + + // Portable key expansion + encrypt + tinyaes::internal::aes_key_expand_portable(key, key_len, rk); + + for (size_t off = 0; off + 16 <= pt_len; off += 16) + { + uint8_t out_portable[16], out_dispatch[16]; + tinyaes::internal::aes_encrypt_block_portable(rk, rounds, plaintext + off, out_portable); + tinyaes::internal::get_encrypt_block()(rk, rounds, plaintext + off, out_dispatch); + assert(std::memcmp(out_portable, out_dispatch, 16) == 0); + + // Also verify decrypt roundtrip + uint8_t dec_portable[16], dec_dispatch[16]; + tinyaes::internal::aes_decrypt_block_portable(rk, rounds, out_portable, dec_portable); + tinyaes::internal::get_decrypt_block()(rk, rounds, out_dispatch, dec_dispatch); + assert(std::memcmp(dec_portable, dec_dispatch, 16) == 0); + assert(std::memcmp(dec_portable, plaintext + off, 16) == 0); + } +} extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - if (size < 16) + if (size < 2) return 0; - // Use first 16 bytes as key, rest as plaintext (block-aligned) - std::vector key(data, data + 16); - size_t pt_len = ((size - 16) / 16) * 16; + // First byte selects key size: 16, 24, or 32 + static const size_t key_sizes[] = {16, 24, 32}; + size_t key_len = key_sizes[data[0] % 3]; + data++; + size--; + + if (size < key_len + 16) + return 0; + + std::vector key(data, data + key_len); + size_t pt_len = ((size - key_len) / 16) * 16; if (pt_len == 0) return 0; - std::vector plaintext(data + 16, data + 16 + pt_len); + std::vector plaintext(data + key_len, data + key_len + pt_len); std::vector ct, pt; auto result = tinyaes::ecb_encrypt(key, plaintext, ct); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return 0; result = tinyaes::ecb_decrypt(key, ct, pt); - assert(result == tinyaes::Result::Success); + assert(result == tinyaes::Result::Ok); assert(pt == plaintext); + // Differential test: portable vs dispatched + diff_encrypt_block(data, key_len, data + key_len, pt_len); + return 0; } diff --git a/fuzz/fuzz_gcm.cpp b/fuzz/fuzz_gcm.cpp index ba48631..f7a9003 100644 --- a/fuzz/fuzz_gcm.cpp +++ b/fuzz/fuzz_gcm.cpp @@ -2,34 +2,117 @@ // BSD 3-Clause License (see LICENSE) #include "tinyaes/gcm.h" +#include "internal/aes_impl.h" +#include "internal/ghash.h" #include #include #include +#include + +// Differential: compare portable GHASH against dispatched +static void diff_ghash(const uint8_t *key, size_t key_len, const uint8_t *data, size_t data_len) +{ + int rounds = tinyaes::internal::aes_rounds(key_len); + if (rounds == 0) + return; + + // Compute H = E_K(0^128) + uint32_t rk[tinyaes::internal::AES_MAX_RK_WORDS]; + tinyaes::internal::aes_key_expand_portable(key, key_len, rk); + + uint8_t H[16] = {0}; + uint8_t zero[16] = {0}; + tinyaes::internal::aes_encrypt_block_portable(rk, rounds, zero, H); + + uint8_t Y_portable[16] = {0}; + uint8_t Y_dispatch[16] = {0}; + + tinyaes::internal::ghash_portable(H, data, data_len, Y_portable); + tinyaes::internal::get_ghash()(H, data, data_len, Y_dispatch); + + assert(std::memcmp(Y_portable, Y_dispatch, 16) == 0); +} + +// Tamper test: flip a bit in ciphertext/tag/aad and verify auth failure +static void tamper_test(const std::vector &key, const std::vector &iv, + const std::vector &aad, const std::vector &ct, + const std::vector &tag, uint8_t tamper_byte) +{ + std::vector pt; + + // Tamper ciphertext (if non-empty) + if (!ct.empty()) + { + std::vector bad_ct = ct; + bad_ct[tamper_byte % bad_ct.size()] ^= 0x01; + auto result = tinyaes::gcm_decrypt(key, iv, aad, bad_ct, tag, pt); + assert(result == tinyaes::Result::AuthenticationFailed); + } + + // Tamper tag + { + std::vector bad_tag = tag; + bad_tag[tamper_byte % 16] ^= 0x01; + auto result = tinyaes::gcm_decrypt(key, iv, aad, ct, bad_tag, pt); + assert(result == tinyaes::Result::AuthenticationFailed); + } + + // Tamper AAD (if non-empty) + if (!aad.empty()) + { + std::vector bad_aad = aad; + bad_aad[tamper_byte % bad_aad.size()] ^= 0x01; + auto result = tinyaes::gcm_decrypt(key, iv, bad_aad, ct, tag, pt); + assert(result == tinyaes::Result::AuthenticationFailed); + } +} extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - if (size < 29) + if (size < 2) return 0; - // First 16 bytes = key, next 12 bytes = IV, 1 byte = AAD length, rest split - std::vector key(data, data + 16); - std::vector iv(data + 16, data + 28); - size_t aad_len = data[28] % (size - 29 + 1); - if (29 + aad_len > size) + // First byte selects key size: 16, 24, or 32 + static const size_t key_sizes[] = {16, 24, 32}; + size_t key_len = key_sizes[data[0] % 3]; + data++; + size--; + + // key_len bytes key + 12 bytes IV + 1 byte AAD length selector + if (size < key_len + 13) + return 0; + + std::vector key(data, data + key_len); + std::vector iv(data + key_len, data + key_len + 12); + size_t header = key_len + 12 + 1; + uint8_t aad_selector = data[key_len + 12]; + size_t remaining = size - header; + size_t aad_len = aad_selector % (remaining + 1); + if (aad_len > remaining) aad_len = 0; - std::vector aad(data + 29, data + 29 + aad_len); - std::vector plaintext(data + 29 + aad_len, data + size); + std::vector aad(data + header, data + header + aad_len); + std::vector plaintext(data + header + aad_len, data + size); std::vector ct, tag, pt; auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return 0; result = tinyaes::gcm_decrypt(key, iv, aad, ct, tag, pt); - assert(result == tinyaes::Result::Success); + assert(result == tinyaes::Result::Ok); assert(pt == plaintext); + // Differential: portable GHASH vs dispatched + size_t total_data = aad_len + plaintext.size(); + if (total_data > 0) + { + diff_ghash(data, key_len, data + header, total_data); + } + + // Tamper test: verify authentication catches single-bit corruption + tamper_test(key, iv, aad, ct, tag, aad_selector); + return 0; } diff --git a/include/tinyaes/cbc.h b/include/tinyaes/cbc.h index 439c18b..cf2df25 100644 --- a/include/tinyaes/cbc.h +++ b/include/tinyaes/cbc.h @@ -106,6 +106,18 @@ namespace tinyaes const std::vector &ciphertext, std::vector &plaintext); + // CBC encrypt with PKCS#7 — library generates IV, prepended to ciphertext + Result cbc_encrypt_pkcs7( + const std::vector &key, + const std::vector &plaintext, + std::vector &iv_and_ciphertext); + + // CBC decrypt with PKCS#7 — IV is first 16 bytes of input + Result cbc_decrypt_pkcs7( + const std::vector &key, + const std::vector &iv_and_ciphertext, + std::vector &plaintext); + } // namespace tinyaes #endif diff --git a/include/tinyaes/common.h b/include/tinyaes/common.h index ba0749c..969e7da 100644 --- a/include/tinyaes/common.h +++ b/include/tinyaes/common.h @@ -56,12 +56,14 @@ #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) +#define TINYAES_OK 0 +#define TINYAES_INVALID_KEY_SIZE (-1) +#define TINYAES_INVALID_IV_SIZE (-2) +#define TINYAES_INVALID_NONCE_SIZE (-3) +#define TINYAES_INVALID_INPUT_SIZE (-4) +#define TINYAES_INVALID_PADDING (-5) +#define TINYAES_AUTH_FAILED (-6) +#define TINYAES_INTERNAL_ERROR (-7) #ifdef __cplusplus extern "C" @@ -70,6 +72,10 @@ extern "C" TINYAES_EXPORT int tinyaes_constant_time_equal(const uint8_t *a, const uint8_t *b, size_t len); + TINYAES_EXPORT int tinyaes_generate_iv(uint8_t out[16]); + + TINYAES_EXPORT int tinyaes_generate_nonce(uint8_t out[12]); + #ifdef __cplusplus } #endif @@ -81,12 +87,14 @@ namespace tinyaes enum class Result { - Success = 0, + Ok = 0, InvalidKeySize = -1, - InvalidInput = -2, - InvalidPadding = -3, - AuthFailed = -4, - BufferTooSmall = -5 + InvalidIVSize = -2, + InvalidNonceSize = -3, + InvalidInputSize = -4, + InvalidPadding = -5, + AuthenticationFailed = -6, + InternalError = -7 }; void secure_zero(void *ptr, size_t len); @@ -102,6 +110,10 @@ namespace tinyaes int generate_iv(uint8_t *out, size_t len); + std::vector generate_iv(); + + std::vector generate_nonce(); + } // namespace tinyaes #endif diff --git a/include/tinyaes/ctr.h b/include/tinyaes/ctr.h index c263c95..da02d4b 100644 --- a/include/tinyaes/ctr.h +++ b/include/tinyaes/ctr.h @@ -33,7 +33,7 @@ extern "C" { #endif - // CTR encrypt/decrypt (symmetric operation) + // CTR encrypt/decrypt (symmetric operation) — raw 16-byte IV TINYAES_EXPORT int tinyaes_ctr_crypt( const uint8_t *key, size_t key_len, @@ -43,6 +43,26 @@ extern "C" uint8_t *output, size_t output_len); + // CTR encrypt — 12-byte nonce (counter starts at 1) + TINYAES_EXPORT int tinyaes_ctr_encrypt( + const uint8_t *key, + size_t key_len, + const uint8_t *nonce, + const uint8_t *plaintext, + size_t plaintext_len, + uint8_t *ciphertext, + size_t ciphertext_len); + + // CTR decrypt — 12-byte nonce + TINYAES_EXPORT int tinyaes_ctr_decrypt( + const uint8_t *key, + size_t key_len, + const uint8_t *nonce, + const uint8_t *ciphertext, + size_t ciphertext_len, + uint8_t *plaintext, + size_t plaintext_len); + #ifdef __cplusplus } #endif @@ -61,6 +81,32 @@ namespace tinyaes const std::vector &input, std::vector &output); + // CTR encrypt — caller provides nonce (12 bytes, counter starts at 1) + Result ctr_encrypt( + const std::vector &key, + const std::vector &nonce, + const std::vector &plaintext, + std::vector &ciphertext); + + // CTR encrypt — library generates nonce, prepended to output + Result ctr_encrypt( + const std::vector &key, + const std::vector &plaintext, + std::vector &nonce_and_ciphertext); + + // CTR decrypt — caller provides nonce + Result ctr_decrypt( + const std::vector &key, + const std::vector &nonce, + const std::vector &ciphertext, + std::vector &plaintext); + + // CTR decrypt — nonce is first 12 bytes of input + Result ctr_decrypt( + const std::vector &key, + const std::vector &nonce_and_ciphertext, + std::vector &plaintext); + } // namespace tinyaes #endif diff --git a/include/tinyaes/gcm.h b/include/tinyaes/gcm.h index 39db48f..a5894ca 100644 --- a/include/tinyaes/gcm.h +++ b/include/tinyaes/gcm.h @@ -86,6 +86,36 @@ namespace tinyaes const std::vector &tag, std::vector &plaintext); + // GCM encrypt — caller provides nonce, tag appended to ciphertext + Result gcm_encrypt( + const std::vector &key, + const std::vector &nonce, + const std::vector &plaintext, + const std::vector &aad, + std::vector &ciphertext_and_tag); + + // GCM encrypt — library generates nonce, output is nonce||ciphertext||tag + Result gcm_encrypt( + const std::vector &key, + const std::vector &plaintext, + const std::vector &aad, + std::vector &nonce_ciphertext_tag); + + // GCM decrypt — caller provides nonce, input is ciphertext||tag + Result gcm_decrypt( + const std::vector &key, + const std::vector &nonce, + const std::vector &ciphertext_and_tag, + const std::vector &aad, + std::vector &plaintext); + + // GCM decrypt — nonce is first 12 bytes of input, rest is ciphertext||tag + Result gcm_decrypt( + const std::vector &key, + const std::vector &nonce_ciphertext_tag, + const std::vector &aad, + std::vector &plaintext); + } // namespace tinyaes #endif diff --git a/src/backend/aes_arm_ce.cpp b/src/backend/aes_arm_ce.cpp index f00ac45..8194888 100644 --- a/src/backend/aes_arm_ce.cpp +++ b/src/backend/aes_arm_ce.cpp @@ -48,6 +48,14 @@ namespace tinyaes // Note: ARM AES instructions combine SubBytes+ShiftRows (vaese) and MixColumns (vaesmc) // The key XOR is done separately (veorq). + // Round keys are stored as big-endian uint32_t by the portable key expansion. + // On little-endian ARM64, the bytes within each 32-bit word are reversed in memory. + // vrev32q_u8 restores the original byte order expected by the ARM AES instructions. + static inline uint8x16_t load_rk(const uint8_t *rk8) + { + return vrev32q_u8(vld1q_u8(rk8)); + } + 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(rk); @@ -57,15 +65,15 @@ namespace tinyaes // 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); + uint8x16_t key = load_rk(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); + uint8x16_t key_last = load_rk(rk8 + (rounds - 1) * 16); block = vaeseq_u8(block, key_last); // Final AddRoundKey - uint8x16_t key_final = vld1q_u8(rk8 + rounds * 16); + uint8x16_t key_final = load_rk(rk8 + rounds * 16); block = veorq_u8(block, key_final); vst1q_u8(out, block); @@ -76,19 +84,22 @@ namespace tinyaes const uint8_t *rk8 = reinterpret_cast(rk); uint8x16_t block = vld1q_u8(in); - // Decryption uses inverse round keys in reverse order - for (int i = rounds; i > 1; --i) + // ARM AESD places AddRoundKey before InvSubBytes, but standard AES + // places it after. Since InvSubBytes is nonlinear, the middle round + // keys must be pre-processed with InvMixColumns to compensate. + // First round key (rk[rounds]) and last round key (rk[0]) are used as-is. + block = vaesdq_u8(block, load_rk(rk8 + rounds * 16)); + block = vaesimcq_u8(block); + + for (int i = rounds - 1; i > 1; --i) { - uint8x16_t key = vld1q_u8(rk8 + i * 16); - block = vaesdq_u8(block, key); + block = vaesdq_u8(block, vaesimcq_u8(load_rk(rk8 + i * 16))); block = vaesimcq_u8(block); } - // Last round: no InvMixColumns - uint8x16_t key_1 = vld1q_u8(rk8 + 16); - block = vaesdq_u8(block, key_1); + // Last round: key needs InvMixColumns, but no InvMixColumns on state + block = vaesdq_u8(block, vaesimcq_u8(load_rk(rk8 + 16))); // Final AddRoundKey - uint8x16_t key_0 = vld1q_u8(rk8); - block = veorq_u8(block, key_0); + block = veorq_u8(block, load_rk(rk8)); vst1q_u8(out, block); } @@ -104,13 +115,13 @@ namespace tinyaes for (int r = 0; r < rounds - 1; ++r) { - uint8x16_t key = vld1q_u8(rk8 + r * 16); + uint8x16_t key = load_rk(rk8 + r * 16); block = vaeseq_u8(block, key); block = vaesmcq_u8(block); } - uint8x16_t key_last = vld1q_u8(rk8 + (rounds - 1) * 16); + uint8x16_t key_last = load_rk(rk8 + (rounds - 1) * 16); block = vaeseq_u8(block, key_last); - uint8x16_t key_final = vld1q_u8(rk8 + rounds * 16); + uint8x16_t key_final = load_rk(rk8 + rounds * 16); block = veorq_u8(block, key_final); // XOR with plaintext diff --git a/src/backend/ghash_arm_ce.cpp b/src/backend/ghash_arm_ce.cpp index 3d483ad..b5091ae 100644 --- a/src/backend/ghash_arm_ce.cpp +++ b/src/backend/ghash_arm_ce.cpp @@ -44,15 +44,16 @@ namespace tinyaes { // GF(2^128) multiplication using PMULL (carry-less multiply) + // Inputs/outputs are in standard bit order (vrbitq_u8 applied): + // lane 0 = x^0..x^63 (low), lane 1 = x^64..x^127 (high) 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 + // Karatsuba multiplication: A(x)*B(x) where A = a_hi*x^64 + a_lo 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); @@ -62,35 +63,42 @@ namespace tinyaes 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); + // Combine mid*x^64 into [hi_v : lo_v] + uint8x16_t mid_lo = vextq_u8(vdupq_n_u8(0), mid, 8); // mid << 64 + uint8x16_t mid_hi = vextq_u8(mid, vdupq_n_u8(0), 8); // mid >> 64 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); + // 256-bit product = [D3:D2:D1:D0] where hi_v=[D3:D2], lo_v=[D1:D0] + // Reduction modulo p(x) = x^128 + x^7 + x^2 + x + 1 + // Since x^128 ≡ x^7+x^2+x+1, let q = 0x87 + poly64_t q = (poly64_t)0x87ULL; - return veorq_u8(veorq_u8(f_swap, vreinterpretq_u8_p128(t2)), hi_v); + // Step 1: Reduce D3 — D3*x^192 ≡ (D3*q)*x^64 + poly128_t t1 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(hi_v), 1), q); + uint8x16_t t1_v = vreinterpretq_u8_p128(t1); + lo_v = veorq_u8(lo_v, vextq_u8(vdupq_n_u8(0), t1_v, 8)); // D1 ^= T1_lo + hi_v = veorq_u8(hi_v, vextq_u8(t1_v, vdupq_n_u8(0), 8)); // D2 ^= T1_hi + + // Step 2: Reduce D2' — D2'*x^128 ≡ D2'*q + poly128_t t2 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(hi_v), 0), q); + lo_v = veorq_u8(lo_v, vreinterpretq_u8_p128(t2)); + + return lo_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); + // GCM uses MSB-first bit ordering (bit 7 of byte 0 = x^0), but ARM PMULL + // uses LSB-first (bit 0 = x^0). vrbitq_u8 reverses bits within each byte + // to convert between these conventions. + uint8x16_t h = vrbitq_u8(vld1q_u8(H)); + uint8x16_t y = vrbitq_u8(vld1q_u8(Y)); 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); + uint8x16_t d = vrbitq_u8(vld1q_u8(data + i * 16)); y = veorq_u8(y, d); y = gf128_mul_pmull(y, h); } @@ -100,12 +108,12 @@ namespace tinyaes { uint8_t padded[16] = {0}; std::memcpy(padded, data + full_blocks * 16, remainder); - uint8x16_t d = vqtbl1q_u8(vld1q_u8(padded), rev_mask); + uint8x16_t d = vrbitq_u8(vld1q_u8(padded)); y = veorq_u8(y, d); y = gf128_mul_pmull(y, h); } - vst1q_u8(Y, vqtbl1q_u8(y, rev_mask)); + vst1q_u8(Y, vrbitq_u8(y)); } } // namespace internal diff --git a/src/cbc.cpp b/src/cbc.cpp index 3f5f70a..3142f61 100644 --- a/src/cbc.cpp +++ b/src/cbc.cpp @@ -42,9 +42,9 @@ namespace tinyaes if (rounds == 0) return Result::InvalidKeySize; if (iv.size() != 16) - return Result::InvalidInput; + return Result::InvalidIVSize; if (plaintext.empty() || (plaintext.size() % 16) != 0) - return Result::InvalidInput; + return Result::InvalidInputSize; uint32_t rk[internal::AES_MAX_RK_WORDS]; auto key_expand = internal::get_key_expand(); @@ -69,7 +69,7 @@ namespace tinyaes secure_zero(rk, sizeof(rk)); secure_zero(block, sizeof(block)); - return Result::Success; + return Result::Ok; } Result cbc_decrypt( @@ -82,9 +82,9 @@ namespace tinyaes if (rounds == 0) return Result::InvalidKeySize; if (iv.size() != 16) - return Result::InvalidInput; + return Result::InvalidIVSize; if (ciphertext.empty() || (ciphertext.size() % 16) != 0) - return Result::InvalidInput; + return Result::InvalidInputSize; uint32_t rk[internal::AES_MAX_RK_WORDS]; auto key_expand = internal::get_key_expand(); @@ -107,7 +107,7 @@ namespace tinyaes } secure_zero(rk, sizeof(rk)); - return Result::Success; + return Result::Ok; } // PKCS#7 padding: append N bytes of value N where N = 16 - (len % 16) @@ -121,7 +121,7 @@ namespace tinyaes size_t pad_len = 16 - (plaintext.size() % 16); std::vector padded(plaintext.size() + pad_len); std::memcpy(padded.data(), plaintext.data(), plaintext.size()); - std::memset(padded.data() + plaintext.size(), static_cast(pad_len), pad_len); + std::memset(padded.data() + plaintext.size(), static_cast(pad_len), pad_len); auto result = cbc_encrypt(key, iv, padded, ciphertext); secure_zero(padded.data(), padded.size()); @@ -137,34 +137,74 @@ namespace tinyaes { std::vector decrypted; auto result = cbc_decrypt(key, iv, ciphertext, decrypted); - if (result != Result::Success) + if (result != Result::Ok) return result; if (decrypted.empty()) return Result::InvalidPadding; - // Constant-time PKCS#7 validation: scan entire last block + // Fully constant-time PKCS#7 validation: always scan all 16 bytes of 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) + // Range check via arithmetic (no branches): + // bad_range is 0xFF if pad_val is out of [1..16], 0x00 otherwise + uint8_t bad_range = static_cast(((pad_val - 1) >> 8) | ((16 - pad_val) >> 8)); + + // Always scan exactly 16 bytes of the last block + const uint8_t *last_block = decrypted.data() + decrypted.size() - 16; + volatile uint8_t bad_pad = 0; + for (unsigned j = 0; j < 16; ++j) { - bad |= static_cast(decrypted[i] ^ pad_val); + // should_be_pad is 0xFF when byte j is in the padding region, 0x00 otherwise + // Padding region: positions where (j + pad_val >= 16), i.e. j >= 16 - pad_val + uint8_t should_be_pad = static_cast((15 - j - pad_val) >> 8); + bad_pad |= static_cast(should_be_pad & (last_block[j] ^ pad_val)); } + uint8_t bad = static_cast(bad_range | bad_pad); if (bad != 0) { secure_zero(decrypted.data(), decrypted.size()); return Result::InvalidPadding; } + size_t start = decrypted.size() - pad_val; plaintext.assign(decrypted.begin(), decrypted.begin() + static_cast(start)); secure_zero(decrypted.data(), decrypted.size()); - return Result::Success; + return Result::Ok; + } + + Result cbc_encrypt_pkcs7( + const std::vector &key, + const std::vector &plaintext, + std::vector &iv_and_ciphertext) + { + auto iv = generate_iv(); + if (iv.empty()) + return Result::InternalError; + + std::vector ct; + auto result = cbc_encrypt_pkcs7(key, iv, plaintext, ct); + if (result != Result::Ok) + return result; + + iv_and_ciphertext.resize(16 + ct.size()); + std::memcpy(iv_and_ciphertext.data(), iv.data(), 16); + std::memcpy(iv_and_ciphertext.data() + 16, ct.data(), ct.size()); + return Result::Ok; + } + + Result cbc_decrypt_pkcs7( + const std::vector &key, + const std::vector &iv_and_ciphertext, + std::vector &plaintext) + { + if (iv_and_ciphertext.size() < 32) + return Result::InvalidInputSize; + + std::vector iv(iv_and_ciphertext.begin(), iv_and_ciphertext.begin() + 16); + std::vector ct(iv_and_ciphertext.begin() + 16, iv_and_ciphertext.end()); + return cbc_decrypt_pkcs7(key, iv, ct, plaintext); } } // namespace tinyaes @@ -180,9 +220,9 @@ extern "C" int tinyaes_cbc_encrypt( size_t ciphertext_len) { if (!key || !iv || !plaintext || !ciphertext) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; if (ciphertext_len < plaintext_len) - return TINYAES_ERROR_BUFFER_TOO_SMALL; + return TINYAES_INVALID_INPUT_SIZE; std::vector k(key, key + key_len); std::vector v(iv, iv + 16); @@ -191,12 +231,13 @@ extern "C" int tinyaes_cbc_encrypt( auto result = tinyaes::cbc_encrypt(k, v, pt, ct); tinyaes::secure_zero(k.data(), k.size()); + tinyaes::secure_zero(pt.data(), pt.size()); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return static_cast(result); std::memcpy(ciphertext, ct.data(), ct.size()); - return TINYAES_SUCCESS; + return TINYAES_OK; } extern "C" int tinyaes_cbc_decrypt( @@ -209,9 +250,9 @@ extern "C" int tinyaes_cbc_decrypt( size_t plaintext_len) { if (!key || !iv || !ciphertext || !plaintext) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; if (plaintext_len < ciphertext_len) - return TINYAES_ERROR_BUFFER_TOO_SMALL; + return TINYAES_INVALID_INPUT_SIZE; std::vector k(key, key + key_len); std::vector v(iv, iv + 16); @@ -221,11 +262,15 @@ extern "C" int tinyaes_cbc_decrypt( auto result = tinyaes::cbc_decrypt(k, v, ct, pt); tinyaes::secure_zero(k.data(), k.size()); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) + { + tinyaes::secure_zero(pt.data(), pt.size()); return static_cast(result); + } std::memcpy(plaintext, pt.data(), pt.size()); - return TINYAES_SUCCESS; + tinyaes::secure_zero(pt.data(), pt.size()); + return TINYAES_OK; } extern "C" int tinyaes_cbc_encrypt_pkcs7( @@ -238,13 +283,13 @@ extern "C" int tinyaes_cbc_encrypt_pkcs7( size_t *ciphertext_len) { if (!key || !iv || !plaintext || !ciphertext || !ciphertext_len) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; 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; + return TINYAES_INVALID_INPUT_SIZE; } std::vector k(key, key + key_len); @@ -254,13 +299,14 @@ extern "C" int tinyaes_cbc_encrypt_pkcs7( auto result = tinyaes::cbc_encrypt_pkcs7(k, v, pt, ct); tinyaes::secure_zero(k.data(), k.size()); + tinyaes::secure_zero(pt.data(), pt.size()); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return static_cast(result); std::memcpy(ciphertext, ct.data(), ct.size()); *ciphertext_len = ct.size(); - return TINYAES_SUCCESS; + return TINYAES_OK; } extern "C" int tinyaes_cbc_decrypt_pkcs7( @@ -273,7 +319,7 @@ extern "C" int tinyaes_cbc_decrypt_pkcs7( size_t *plaintext_len) { if (!key || !iv || !ciphertext || !plaintext || !plaintext_len) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; std::vector k(key, key + key_len); std::vector v(iv, iv + 16); @@ -283,18 +329,18 @@ extern "C" int tinyaes_cbc_decrypt_pkcs7( auto result = tinyaes::cbc_decrypt_pkcs7(k, v, ct, pt); tinyaes::secure_zero(k.data(), k.size()); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return static_cast(result); if (*plaintext_len < pt.size()) { *plaintext_len = pt.size(); tinyaes::secure_zero(pt.data(), pt.size()); - return TINYAES_ERROR_BUFFER_TOO_SMALL; + return TINYAES_INVALID_INPUT_SIZE; } std::memcpy(plaintext, pt.data(), pt.size()); *plaintext_len = pt.size(); tinyaes::secure_zero(pt.data(), pt.size()); - return TINYAES_SUCCESS; + return TINYAES_OK; } diff --git a/src/ctr.cpp b/src/ctr.cpp index 4dd251e..7317ad4 100644 --- a/src/ctr.cpp +++ b/src/ctr.cpp @@ -43,9 +43,9 @@ namespace tinyaes if (rounds == 0) return Result::InvalidKeySize; if (iv.size() != 16) - return Result::InvalidInput; + return Result::InvalidIVSize; if (input.empty()) - return Result::InvalidInput; + return Result::InvalidInputSize; uint32_t rk[internal::AES_MAX_RK_WORDS]; auto key_expand = internal::get_key_expand(); @@ -83,7 +83,73 @@ namespace tinyaes secure_zero(rk, sizeof(rk)); secure_zero(ctr, sizeof(ctr)); - return Result::Success; + return Result::Ok; + } + + // Build a 16-byte IV from 12-byte nonce + 4-byte counter starting at 1 + static std::vector nonce_to_iv(const std::vector &nonce) + { + std::vector iv(16, 0); + std::memcpy(iv.data(), nonce.data(), 12); + iv[15] = 1; // big-endian counter = 1 + return iv; + } + + Result ctr_encrypt( + const std::vector &key, + const std::vector &nonce, + const std::vector &plaintext, + std::vector &ciphertext) + { + if (nonce.size() != 12) + return Result::InvalidNonceSize; + auto iv = nonce_to_iv(nonce); + return ctr_crypt(key, iv, plaintext, ciphertext); + } + + Result ctr_encrypt( + const std::vector &key, + const std::vector &plaintext, + std::vector &nonce_and_ciphertext) + { + auto nonce = generate_nonce(); + if (nonce.empty()) + return Result::InternalError; + + std::vector ct; + auto result = ctr_encrypt(key, nonce, plaintext, ct); + if (result != Result::Ok) + return result; + + nonce_and_ciphertext.resize(12 + ct.size()); + std::memcpy(nonce_and_ciphertext.data(), nonce.data(), 12); + std::memcpy(nonce_and_ciphertext.data() + 12, ct.data(), ct.size()); + return Result::Ok; + } + + Result ctr_decrypt( + const std::vector &key, + const std::vector &nonce, + const std::vector &ciphertext, + std::vector &plaintext) + { + if (nonce.size() != 12) + return Result::InvalidNonceSize; + auto iv = nonce_to_iv(nonce); + return ctr_crypt(key, iv, ciphertext, plaintext); + } + + Result ctr_decrypt( + const std::vector &key, + const std::vector &nonce_and_ciphertext, + std::vector &plaintext) + { + if (nonce_and_ciphertext.size() < 13) + return Result::InvalidInputSize; + + std::vector nonce(nonce_and_ciphertext.begin(), nonce_and_ciphertext.begin() + 12); + std::vector ct(nonce_and_ciphertext.begin() + 12, nonce_and_ciphertext.end()); + return ctr_decrypt(key, nonce, ct, plaintext); } } // namespace tinyaes @@ -98,9 +164,9 @@ extern "C" int tinyaes_ctr_crypt( size_t output_len) { if (!key || !iv || !input || !output) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; if (output_len < input_len) - return TINYAES_ERROR_BUFFER_TOO_SMALL; + return TINYAES_INVALID_INPUT_SIZE; std::vector k(key, key + key_len); std::vector v(iv, iv + 16); @@ -109,10 +175,74 @@ extern "C" int tinyaes_ctr_crypt( auto result = tinyaes::ctr_crypt(k, v, in, out); tinyaes::secure_zero(k.data(), k.size()); + tinyaes::secure_zero(in.data(), in.size()); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return static_cast(result); std::memcpy(output, out.data(), out.size()); - return TINYAES_SUCCESS; + return TINYAES_OK; +} + +extern "C" int tinyaes_ctr_encrypt( + const uint8_t *key, + size_t key_len, + const uint8_t *nonce, + const uint8_t *plaintext, + size_t plaintext_len, + uint8_t *ciphertext, + size_t ciphertext_len) +{ + if (!key || !nonce || !plaintext || !ciphertext) + return TINYAES_INVALID_INPUT_SIZE; + if (ciphertext_len < plaintext_len) + return TINYAES_INVALID_INPUT_SIZE; + + std::vector k(key, key + key_len); + std::vector n(nonce, nonce + 12); + std::vector pt(plaintext, plaintext + plaintext_len); + std::vector ct; + + auto result = tinyaes::ctr_encrypt(k, n, pt, ct); + tinyaes::secure_zero(k.data(), k.size()); + tinyaes::secure_zero(pt.data(), pt.size()); + + if (result != tinyaes::Result::Ok) + return static_cast(result); + + std::memcpy(ciphertext, ct.data(), ct.size()); + return TINYAES_OK; +} + +extern "C" int tinyaes_ctr_decrypt( + const uint8_t *key, + size_t key_len, + const uint8_t *nonce, + const uint8_t *ciphertext, + size_t ciphertext_len, + uint8_t *plaintext, + size_t plaintext_len) +{ + if (!key || !nonce || !ciphertext || !plaintext) + return TINYAES_INVALID_INPUT_SIZE; + if (plaintext_len < ciphertext_len) + return TINYAES_INVALID_INPUT_SIZE; + + std::vector k(key, key + key_len); + std::vector n(nonce, nonce + 12); + std::vector ct(ciphertext, ciphertext + ciphertext_len); + std::vector pt; + + auto result = tinyaes::ctr_decrypt(k, n, ct, pt); + tinyaes::secure_zero(k.data(), k.size()); + + if (result != tinyaes::Result::Ok) + { + tinyaes::secure_zero(pt.data(), pt.size()); + return static_cast(result); + } + + std::memcpy(plaintext, pt.data(), pt.size()); + tinyaes::secure_zero(pt.data(), pt.size()); + return TINYAES_OK; } diff --git a/src/ecb.cpp b/src/ecb.cpp index d11f4b6..f3a2194 100644 --- a/src/ecb.cpp +++ b/src/ecb.cpp @@ -41,7 +41,7 @@ namespace tinyaes if (rounds == 0) return Result::InvalidKeySize; if (plaintext.empty() || (plaintext.size() % 16) != 0) - return Result::InvalidInput; + return Result::InvalidInputSize; uint32_t rk[internal::AES_MAX_RK_WORDS]; auto key_expand = internal::get_key_expand(); @@ -56,7 +56,7 @@ namespace tinyaes } secure_zero(rk, sizeof(rk)); - return Result::Success; + return Result::Ok; } Result ecb_decrypt( @@ -68,7 +68,7 @@ namespace tinyaes if (rounds == 0) return Result::InvalidKeySize; if (ciphertext.empty() || (ciphertext.size() % 16) != 0) - return Result::InvalidInput; + return Result::InvalidInputSize; uint32_t rk[internal::AES_MAX_RK_WORDS]; auto key_expand = internal::get_key_expand(); @@ -83,7 +83,7 @@ namespace tinyaes } secure_zero(rk, sizeof(rk)); - return Result::Success; + return Result::Ok; } } // namespace tinyaes @@ -97,9 +97,9 @@ extern "C" int tinyaes_ecb_encrypt( size_t ciphertext_len) { if (!key || !plaintext || !ciphertext) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; if (ciphertext_len < plaintext_len) - return TINYAES_ERROR_BUFFER_TOO_SMALL; + return TINYAES_INVALID_INPUT_SIZE; std::vector k(key, key + key_len); std::vector pt(plaintext, plaintext + plaintext_len); @@ -107,12 +107,13 @@ extern "C" int tinyaes_ecb_encrypt( auto result = tinyaes::ecb_encrypt(k, pt, ct); tinyaes::secure_zero(k.data(), k.size()); + tinyaes::secure_zero(pt.data(), pt.size()); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return static_cast(result); std::memcpy(ciphertext, ct.data(), ct.size()); - return TINYAES_SUCCESS; + return TINYAES_OK; } extern "C" int tinyaes_ecb_decrypt( @@ -124,9 +125,9 @@ extern "C" int tinyaes_ecb_decrypt( size_t plaintext_len) { if (!key || !ciphertext || !plaintext) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; if (plaintext_len < ciphertext_len) - return TINYAES_ERROR_BUFFER_TOO_SMALL; + return TINYAES_INVALID_INPUT_SIZE; std::vector k(key, key + key_len); std::vector ct(ciphertext, ciphertext + ciphertext_len); @@ -135,9 +136,13 @@ extern "C" int tinyaes_ecb_decrypt( auto result = tinyaes::ecb_decrypt(k, ct, pt); tinyaes::secure_zero(k.data(), k.size()); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) + { + tinyaes::secure_zero(pt.data(), pt.size()); return static_cast(result); + } std::memcpy(plaintext, pt.data(), pt.size()); - return TINYAES_SUCCESS; + tinyaes::secure_zero(pt.data(), pt.size()); + return TINYAES_OK; } diff --git a/src/gcm.cpp b/src/gcm.cpp index e341c28..95cffa2 100644 --- a/src/gcm.cpp +++ b/src/gcm.cpp @@ -123,7 +123,7 @@ namespace tinyaes if (rounds == 0) return Result::InvalidKeySize; if (iv.empty()) - return Result::InvalidInput; + return Result::InvalidIVSize; uint32_t rk[internal::AES_MAX_RK_WORDS]; auto key_expand = internal::get_key_expand(); @@ -180,7 +180,7 @@ namespace tinyaes secure_zero(rk, sizeof(rk)); secure_zero(H, sizeof(H)); secure_zero(J0, sizeof(J0)); - return Result::Success; + return Result::Ok; } Result gcm_decrypt( @@ -195,9 +195,9 @@ namespace tinyaes if (rounds == 0) return Result::InvalidKeySize; if (iv.empty()) - return Result::InvalidInput; + return Result::InvalidIVSize; if (tag.size() != 16) - return Result::InvalidInput; + return Result::InvalidInputSize; uint32_t rk[internal::AES_MAX_RK_WORDS]; auto key_expand = internal::get_key_expand(); @@ -226,7 +226,7 @@ namespace tinyaes secure_zero(H, sizeof(H)); secure_zero(J0, sizeof(J0)); secure_zero(computed_tag, sizeof(computed_tag)); - return Result::AuthFailed; + return Result::AuthenticationFailed; } // Tag verified — decrypt ciphertext using CTR mode @@ -264,7 +264,77 @@ namespace tinyaes secure_zero(H, sizeof(H)); secure_zero(J0, sizeof(J0)); secure_zero(computed_tag, sizeof(computed_tag)); - return Result::Success; + return Result::Ok; + } + + Result gcm_encrypt( + const std::vector &key, + const std::vector &nonce, + const std::vector &plaintext, + const std::vector &aad, + std::vector &ciphertext_and_tag) + { + std::vector ct, tag; + auto result = gcm_encrypt(key, nonce, aad, plaintext, ct, tag); + if (result != Result::Ok) + return result; + + ciphertext_and_tag.resize(ct.size() + 16); + if (!ct.empty()) + std::memcpy(ciphertext_and_tag.data(), ct.data(), ct.size()); + std::memcpy(ciphertext_and_tag.data() + ct.size(), tag.data(), 16); + return Result::Ok; + } + + Result gcm_encrypt( + const std::vector &key, + const std::vector &plaintext, + const std::vector &aad, + std::vector &nonce_ciphertext_tag) + { + auto nonce = generate_nonce(); + if (nonce.empty()) + return Result::InternalError; + + std::vector ct_tag; + auto result = gcm_encrypt(key, nonce, plaintext, aad, ct_tag); + if (result != Result::Ok) + return result; + + nonce_ciphertext_tag.resize(12 + ct_tag.size()); + std::memcpy(nonce_ciphertext_tag.data(), nonce.data(), 12); + std::memcpy(nonce_ciphertext_tag.data() + 12, ct_tag.data(), ct_tag.size()); + return Result::Ok; + } + + Result gcm_decrypt( + const std::vector &key, + const std::vector &nonce, + const std::vector &ciphertext_and_tag, + const std::vector &aad, + std::vector &plaintext) + { + if (ciphertext_and_tag.size() < 16) + return Result::InvalidInputSize; + + size_t ct_len = ciphertext_and_tag.size() - 16; + std::vector ct(ciphertext_and_tag.begin(), ciphertext_and_tag.begin() + static_cast(ct_len)); + std::vector tag(ciphertext_and_tag.end() - 16, ciphertext_and_tag.end()); + return gcm_decrypt(key, nonce, aad, ct, tag, plaintext); + } + + Result gcm_decrypt( + const std::vector &key, + const std::vector &nonce_ciphertext_tag, + const std::vector &aad, + std::vector &plaintext) + { + if (nonce_ciphertext_tag.size() < 28) // 12 nonce + 16 tag minimum + return Result::InvalidInputSize; + + std::vector nonce(nonce_ciphertext_tag.begin(), nonce_ciphertext_tag.begin() + 12); + std::vector ct_tag(nonce_ciphertext_tag.begin() + 12, nonce_ciphertext_tag.end()); + return gcm_decrypt(key, nonce, ct_tag, aad, plaintext); } } // namespace tinyaes @@ -283,32 +353,33 @@ extern "C" int tinyaes_gcm_encrypt( uint8_t tag[16]) { if (!key || !iv || !tag) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; if (plaintext_len > 0 && (!plaintext || !ciphertext)) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; if (ciphertext_len < plaintext_len) - return TINYAES_ERROR_BUFFER_TOO_SMALL; + return TINYAES_INVALID_INPUT_SIZE; std::vector k(key, key + key_len); std::vector v(iv, iv + iv_len); - std::vector a(aad ? aad : key, aad ? aad + aad_len : key); - if (!aad) - a.clear(); - std::vector pt(plaintext ? plaintext : key, plaintext ? plaintext + plaintext_len : key); - if (!plaintext) - pt.clear(); + std::vector a; + if (aad && aad_len > 0) + a.assign(aad, aad + aad_len); + std::vector pt; + if (plaintext && plaintext_len > 0) + pt.assign(plaintext, plaintext + plaintext_len); std::vector ct, t; auto result = tinyaes::gcm_encrypt(k, v, a, pt, ct, t); tinyaes::secure_zero(k.data(), k.size()); + tinyaes::secure_zero(pt.data(), pt.size()); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) return static_cast(result); if (!ct.empty()) std::memcpy(ciphertext, ct.data(), ct.size()); std::memcpy(tag, t.data(), 16); - return TINYAES_SUCCESS; + return TINYAES_OK; } extern "C" int tinyaes_gcm_decrypt( @@ -325,30 +396,34 @@ extern "C" int tinyaes_gcm_decrypt( const uint8_t tag[16]) { if (!key || !iv || !tag) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; if (ciphertext_len > 0 && (!ciphertext || !plaintext)) - return TINYAES_ERROR_INVALID_INPUT; + return TINYAES_INVALID_INPUT_SIZE; if (plaintext_len < ciphertext_len) - return TINYAES_ERROR_BUFFER_TOO_SMALL; + return TINYAES_INVALID_INPUT_SIZE; std::vector k(key, key + key_len); std::vector v(iv, iv + iv_len); - std::vector a(aad ? aad : key, aad ? aad + aad_len : key); - if (!aad) - a.clear(); - std::vector ct(ciphertext ? ciphertext : key, ciphertext ? ciphertext + ciphertext_len : key); - if (!ciphertext) - ct.clear(); + std::vector a; + if (aad && aad_len > 0) + a.assign(aad, aad + aad_len); + std::vector ct; + if (ciphertext && ciphertext_len > 0) + ct.assign(ciphertext, ciphertext + ciphertext_len); std::vector t(tag, tag + 16); std::vector pt; auto result = tinyaes::gcm_decrypt(k, v, a, ct, t, pt); tinyaes::secure_zero(k.data(), k.size()); - if (result != tinyaes::Result::Success) + if (result != tinyaes::Result::Ok) + { + tinyaes::secure_zero(pt.data(), pt.size()); return static_cast(result); + } if (!pt.empty()) std::memcpy(plaintext, pt.data(), pt.size()); - return TINYAES_SUCCESS; + tinyaes::secure_zero(pt.data(), pt.size()); + return TINYAES_OK; } diff --git a/src/iv_generate.cpp b/src/iv_generate.cpp index bf86644..ee533ae 100644 --- a/src/iv_generate.cpp +++ b/src/iv_generate.cpp @@ -63,4 +63,30 @@ namespace tinyaes #endif } + std::vector generate_iv() + { + std::vector iv(16); + if (generate_iv(iv.data(), 16) != 0) + iv.clear(); + return iv; + } + + std::vector generate_nonce() + { + std::vector nonce(12); + if (generate_iv(nonce.data(), 12) != 0) + nonce.clear(); + return nonce; + } + } // namespace tinyaes + +extern "C" int tinyaes_generate_iv(uint8_t out[16]) +{ + return tinyaes::generate_iv(out, 16); +} + +extern "C" int tinyaes_generate_nonce(uint8_t out[12]) +{ + return tinyaes::generate_iv(out, 12); +} diff --git a/src/secure_zero.cpp b/src/secure_zero.cpp index 2762f64..e8ed161 100644 --- a/src/secure_zero.cpp +++ b/src/secure_zero.cpp @@ -68,8 +68,8 @@ namespace tinyaes { diff |= static_cast(a[i] ^ b[i]); } - volatile uint8_t result = static_cast(diff == 0); - return result != 0; + uint8_t d = diff; // volatile read is the barrier + return d == 0; } } // namespace tinyaes diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7283bd3..b6b9002 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") set_property(TARGET tinyaes_tests APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-z,relro,-z,now -Wl,-z,noexecstack") endif() + # macOS: -bind_at_load is deprecated on modern macOS (eager binding is the default) if(MINGW) set_property(TARGET tinyaes_tests APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--nxcompat -Wl,--dynamicbase -Wl,--high-entropy-va") diff --git a/tests/test_cbc.cpp b/tests/test_cbc.cpp index 8bf81bd..576b208 100644 --- a/tests/test_cbc.cpp +++ b/tests/test_cbc.cpp @@ -12,7 +12,7 @@ TEST(cbc_aes128_encrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(cbc_128_cipher)); } @@ -20,7 +20,7 @@ TEST(cbc_aes128_decrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(cbc_128_plain)); } @@ -28,7 +28,7 @@ TEST(cbc_aes192_encrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(cbc_192_cipher)); } @@ -36,7 +36,7 @@ TEST(cbc_aes192_decrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(cbc_192_plain)); } @@ -44,7 +44,7 @@ TEST(cbc_aes256_encrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(cbc_256_cipher)); } @@ -52,7 +52,7 @@ TEST(cbc_aes256_decrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(cbc_256_plain)); } @@ -64,18 +64,66 @@ TEST(cbc_roundtrip_pkcs7) std::vector ct, pt; auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, plaintext); } TEST(cbc_invalid_iv_size) { std::vector key(16, 0), iv(15, 0), pt(16, 0), ct; - ASSERT_TRUE(tinyaes::cbc_encrypt(key, iv, pt, ct) == tinyaes::Result::InvalidInput); + ASSERT_TRUE(tinyaes::cbc_encrypt(key, iv, pt, ct) == tinyaes::Result::InvalidIVSize); +} + +TEST(cbc_non_block_aligned_no_padding) +{ + std::vector key(16, 0), iv(16, 0), pt(17, 0), ct; + ASSERT_TRUE(tinyaes::cbc_encrypt(key, iv, pt, ct) == tinyaes::Result::InvalidInputSize); +} + +TEST(cbc_multi_block_roundtrip_no_padding) +{ + std::vector key(16, 0x42); + std::vector iv(16, 0x00); + std::vector plaintext(64, 0x55); // 4 blocks + std::vector ct, pt; + + auto result = tinyaes::cbc_encrypt(key, iv, plaintext, ct); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(ct.size() == 64); + + result = tinyaes::cbc_decrypt(key, iv, ct, pt); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_EQ(pt, plaintext); +} + +TEST(cbc_auto_iv_roundtrip) +{ + std::vector key(16, 0x42); + std::vector plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f}; + std::vector iv_ct, pt; + + auto result = tinyaes::cbc_encrypt_pkcs7(key, plaintext, iv_ct); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(iv_ct.size() >= 32); // 16 IV + at least 16 ciphertext + + result = tinyaes::cbc_decrypt_pkcs7(key, iv_ct, pt); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_EQ(pt, plaintext); +} + +TEST(cbc_invalid_key_size) +{ + std::vector key(15, 0x42); // invalid: not 16/24/32 + std::vector iv(16, 0x00); + std::vector plaintext(16, 0x55); + std::vector ct; + + auto result = tinyaes::cbc_encrypt(key, iv, plaintext, ct); + ASSERT_TRUE(result == tinyaes::Result::InvalidKeySize); } #undef VEC diff --git a/tests/test_ctr.cpp b/tests/test_ctr.cpp index 4d7f0c6..714a80d 100644 --- a/tests/test_ctr.cpp +++ b/tests/test_ctr.cpp @@ -12,7 +12,7 @@ TEST(ctr_aes128_encrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(ctr_128_cipher)); } @@ -20,7 +20,7 @@ TEST(ctr_aes128_decrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(ctr_128_plain)); } @@ -28,7 +28,7 @@ TEST(ctr_aes192_encrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(ctr_192_cipher)); } @@ -36,7 +36,7 @@ TEST(ctr_aes256_encrypt) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(ctr_256_cipher)); } @@ -49,12 +49,12 @@ TEST(ctr_partial_block) std::vector ct, pt; auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_TRUE(ct.size() == 7); // Decrypt should recover original result = tinyaes::ctr_crypt(key, iv, ct, pt); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, plaintext); } @@ -66,12 +66,76 @@ TEST(ctr_roundtrip_multi_block) std::vector ct, pt; auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_TRUE(ct.size() == 100); result = tinyaes::ctr_crypt(key, iv, ct, pt); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, plaintext); } +TEST(ctr_zero_length_plaintext) +{ + std::vector key(16, 0x42); + std::vector iv(16, 0x00); + std::vector plaintext; + std::vector ct; + + auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct); + ASSERT_TRUE(result == tinyaes::Result::InvalidInputSize); +} + +TEST(ctr_invalid_nonce_size) +{ + std::vector key(16, 0x42); + std::vector nonce(10, 0x00); // wrong size + std::vector plaintext = {0x01, 0x02, 0x03}; + std::vector ct; + + auto result = tinyaes::ctr_encrypt(key, nonce, plaintext, ct); + ASSERT_TRUE(result == tinyaes::Result::InvalidNonceSize); +} + +TEST(ctr_nonce_encrypt_decrypt_roundtrip) +{ + std::vector key(16, 0x42); + std::vector nonce(12, 0x01); + std::vector plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f}; + std::vector ct, pt; + + auto result = tinyaes::ctr_encrypt(key, nonce, plaintext, ct); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(ct.size() == 5); + + result = tinyaes::ctr_decrypt(key, nonce, ct, pt); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_EQ(pt, plaintext); +} + +TEST(ctr_auto_nonce_roundtrip) +{ + std::vector key(16, 0x42); + std::vector plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f}; + std::vector nonce_ct, pt; + + auto result = tinyaes::ctr_encrypt(key, plaintext, nonce_ct); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(nonce_ct.size() == 17); // 12 nonce + 5 ciphertext + + result = tinyaes::ctr_decrypt(key, nonce_ct, pt); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_EQ(pt, plaintext); +} + +TEST(ctr_invalid_key_size) +{ + std::vector key(15, 0x42); // invalid: not 16/24/32 + std::vector iv(16, 0x00); + std::vector plaintext = {0x01, 0x02, 0x03}; + std::vector ct; + + auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct); + ASSERT_TRUE(result == tinyaes::Result::InvalidKeySize); +} + #undef VEC diff --git a/tests/test_ecb.cpp b/tests/test_ecb.cpp index 6c6e163..0654286 100644 --- a/tests/test_ecb.cpp +++ b/tests/test_ecb.cpp @@ -12,7 +12,7 @@ TEST(ecb_aes128_encrypt) { std::vector ct; auto result = tinyaes::ecb_encrypt(VEC(ecb_128_key), VEC(ecb_128_plain), ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(ecb_128_cipher)); } @@ -20,7 +20,7 @@ TEST(ecb_aes128_decrypt) { std::vector pt; auto result = tinyaes::ecb_decrypt(VEC(ecb_128_key), VEC(ecb_128_cipher), pt); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(ecb_128_plain)); } @@ -28,7 +28,7 @@ TEST(ecb_aes192_encrypt) { std::vector ct; auto result = tinyaes::ecb_encrypt(VEC(ecb_192_key), VEC(ecb_192_plain), ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(ecb_192_cipher)); } @@ -36,7 +36,7 @@ TEST(ecb_aes192_decrypt) { std::vector pt; auto result = tinyaes::ecb_decrypt(VEC(ecb_192_key), VEC(ecb_192_cipher), pt); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(ecb_192_plain)); } @@ -44,7 +44,7 @@ TEST(ecb_aes256_encrypt) { std::vector ct; auto result = tinyaes::ecb_encrypt(VEC(ecb_256_key), VEC(ecb_256_plain), ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(ecb_256_cipher)); } @@ -52,7 +52,7 @@ TEST(ecb_aes256_decrypt) { std::vector pt; auto result = tinyaes::ecb_decrypt(VEC(ecb_256_key), VEC(ecb_256_cipher), pt); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(ecb_256_plain)); } @@ -60,7 +60,7 @@ TEST(ecb_aes128_multi_block) { std::vector ct; auto result = tinyaes::ecb_encrypt(VEC(ecb_128_multi_key), VEC(ecb_128_multi_plain), ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(ecb_128_multi_cipher)); } @@ -68,7 +68,7 @@ TEST(ecb_aes128_multi_block_decrypt) { std::vector pt; auto result = tinyaes::ecb_decrypt(VEC(ecb_128_multi_key), VEC(ecb_128_multi_cipher), pt); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(ecb_128_multi_plain)); } @@ -81,13 +81,13 @@ TEST(ecb_invalid_key_size) TEST(ecb_non_block_aligned) { std::vector key(16, 0), pt(17, 0), ct; - ASSERT_TRUE(tinyaes::ecb_encrypt(key, pt, ct) == tinyaes::Result::InvalidInput); + ASSERT_TRUE(tinyaes::ecb_encrypt(key, pt, ct) == tinyaes::Result::InvalidInputSize); } TEST(ecb_empty_input) { std::vector key(16, 0), pt, ct; - ASSERT_TRUE(tinyaes::ecb_encrypt(key, pt, ct) == tinyaes::Result::InvalidInput); + ASSERT_TRUE(tinyaes::ecb_encrypt(key, pt, ct) == tinyaes::Result::InvalidInputSize); } #undef VEC diff --git a/tests/test_gcm.cpp b/tests/test_gcm.cpp index 45b6a35..b915256 100644 --- a/tests/test_gcm.cpp +++ b/tests/test_gcm.cpp @@ -13,7 +13,7 @@ TEST(gcm_tc1_aes128_no_plaintext_no_aad) { std::vector 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(result == tinyaes::Result::Ok); ASSERT_TRUE(ct.empty()); ASSERT_EQ(tag, VEC(gcm_tc1_tag)); } @@ -22,7 +22,7 @@ TEST(gcm_tc2_aes128_16byte_plaintext) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(gcm_tc2_cipher)); ASSERT_EQ(tag, VEC(gcm_tc2_tag)); } @@ -31,7 +31,7 @@ TEST(gcm_tc3_aes128_64byte_plaintext) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(gcm_tc3_cipher)); ASSERT_EQ(tag, VEC(gcm_tc3_tag)); } @@ -41,7 +41,7 @@ TEST(gcm_tc4_aes128_with_aad) std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(gcm_tc4_cipher)); ASSERT_EQ(tag, VEC(gcm_tc4_tag)); } @@ -50,7 +50,7 @@ TEST(gcm_tc13_aes256_no_plaintext_no_aad) { std::vector 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(result == tinyaes::Result::Ok); ASSERT_TRUE(ct.empty()); ASSERT_EQ(tag, VEC(gcm_tc13_tag)); } @@ -59,7 +59,7 @@ TEST(gcm_tc14_aes256_16byte_plaintext) { std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(ct, VEC(gcm_tc14_cipher)); ASSERT_EQ(tag, VEC(gcm_tc14_tag)); } @@ -69,7 +69,7 @@ TEST(gcm_tc2_decrypt_verify) std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(gcm_tc2_plain)); } @@ -78,7 +78,7 @@ TEST(gcm_tc3_decrypt_verify) std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(gcm_tc3_plain)); } @@ -87,7 +87,7 @@ TEST(gcm_tc4_decrypt_verify) std::vector 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, VEC(gcm_tc4_plain)); } @@ -100,12 +100,128 @@ TEST(gcm_roundtrip) std::vector ct, tag, pt; auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); result = tinyaes::gcm_decrypt(key, iv, aad, ct, tag, pt); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, plaintext); } +TEST(gcm_invalid_nonce_size) +{ + std::vector key(16, 0x42); + std::vector iv; // empty + std::vector plaintext = {0x01}; + std::vector ct, tag; + + auto result = tinyaes::gcm_encrypt(key, iv, EMPTY_VEC, plaintext, ct, tag); + ASSERT_TRUE(result == tinyaes::Result::InvalidIVSize); +} + +TEST(gcm_combined_ct_tag_roundtrip) +{ + std::vector key(16, 0x42); + std::vector nonce(12, 0x01); + std::vector aad = {0xAA, 0xBB}; + std::vector plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f}; + std::vector ct_tag, pt; + + auto result = tinyaes::gcm_encrypt(key, nonce, plaintext, aad, ct_tag); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(ct_tag.size() == 21); // 5 ct + 16 tag + + result = tinyaes::gcm_decrypt(key, nonce, ct_tag, aad, pt); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_EQ(pt, plaintext); +} + +TEST(gcm_auto_nonce_roundtrip) +{ + std::vector key(16, 0x42); + std::vector aad = {0xCC}; + std::vector plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f}; + std::vector nonce_ct_tag, pt; + + auto result = tinyaes::gcm_encrypt(key, plaintext, aad, nonce_ct_tag); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(nonce_ct_tag.size() == 33); // 12 nonce + 5 ct + 16 tag + + result = tinyaes::gcm_decrypt(key, nonce_ct_tag, aad, pt); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_EQ(pt, plaintext); +} + +TEST(gcm_invalid_key_size) +{ + std::vector key(15, 0x42); // invalid: not 16/24/32 + std::vector iv(12, 0x01); + std::vector plaintext = {0x01}; + std::vector ct, tag; + + auto result = tinyaes::gcm_encrypt(key, iv, EMPTY_VEC, plaintext, ct, tag); + ASSERT_TRUE(result == tinyaes::Result::InvalidKeySize); +} + +TEST(gcm_aad_only_no_plaintext) +{ + std::vector key(16, 0x42); + std::vector iv(12, 0x01); + std::vector aad = {0xAA, 0xBB, 0xCC, 0xDD}; + std::vector ct, tag, pt; + + auto result = tinyaes::gcm_encrypt(key, iv, aad, EMPTY_VEC, ct, tag); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(ct.empty()); + ASSERT_TRUE(tag.size() == 16); + + // Decrypt with correct AAD + result = tinyaes::gcm_decrypt(key, iv, aad, ct, tag, pt); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(pt.empty()); + + // Tampered AAD must fail + std::vector bad_aad = aad; + bad_aad[0] ^= 0x01; + result = tinyaes::gcm_decrypt(key, iv, bad_aad, ct, tag, pt); + ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed); +} + +TEST(gcm_tc7_aes192_no_plaintext_no_aad) +{ + std::vector ct, tag; + auto result = tinyaes::gcm_encrypt(VEC(gcm_tc7_key), VEC(gcm_tc7_iv), EMPTY_VEC, EMPTY_VEC, ct, tag); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(ct.empty()); + ASSERT_EQ(tag, VEC(gcm_tc7_tag)); +} + +TEST(gcm_tc8_aes192_16byte_plaintext) +{ + std::vector ct, tag; + auto result = tinyaes::gcm_encrypt(VEC(gcm_tc8_key), VEC(gcm_tc8_iv), EMPTY_VEC, VEC(gcm_tc8_plain), ct, tag); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_EQ(ct, VEC(gcm_tc8_cipher)); + ASSERT_EQ(tag, VEC(gcm_tc8_tag)); +} + +TEST(gcm_tc8_decrypt_verify) +{ + std::vector pt; + auto result = + tinyaes::gcm_decrypt(VEC(gcm_tc8_key), VEC(gcm_tc8_iv), EMPTY_VEC, VEC(gcm_tc8_cipher), VEC(gcm_tc8_tag), pt); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_EQ(pt, VEC(gcm_tc8_plain)); +} + +TEST(gcm_tc8_tampered_tag) +{ + std::vector bad_tag = VEC(gcm_tc8_tag); + bad_tag[0] ^= 0x01; + std::vector pt; + auto result = + tinyaes::gcm_decrypt(VEC(gcm_tc8_key), VEC(gcm_tc8_iv), EMPTY_VEC, VEC(gcm_tc8_cipher), bad_tag, pt); + ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed); +} + #undef VEC #undef EMPTY_VEC diff --git a/tests/test_gcm_auth_failure.cpp b/tests/test_gcm_auth_failure.cpp index ced866e..3a079b1 100644 --- a/tests/test_gcm_auth_failure.cpp +++ b/tests/test_gcm_auth_failure.cpp @@ -35,7 +35,7 @@ TEST(gcm_auth_fail_tampered_ciphertext) std::vector pt; auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), {}, ct, tag, pt); - ASSERT_TRUE(result == tinyaes::Result::AuthFailed); + ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed); ASSERT_TRUE(pt.empty()); } @@ -49,7 +49,7 @@ TEST(gcm_auth_fail_tampered_tag) std::vector pt; auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), {}, ct, tag, pt); - ASSERT_TRUE(result == tinyaes::Result::AuthFailed); + ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed); ASSERT_TRUE(pt.empty()); } @@ -64,7 +64,7 @@ TEST(gcm_auth_fail_tampered_aad) std::vector pt; auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), bad_aad, ct, tag, pt); - ASSERT_TRUE(result == tinyaes::Result::AuthFailed); + ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed); ASSERT_TRUE(pt.empty()); } @@ -78,7 +78,7 @@ TEST(gcm_auth_fail_wrong_key) std::vector pt; auto result = tinyaes::gcm_decrypt(wrong_key, make_iv(), {}, ct, tag, pt); - ASSERT_TRUE(result == tinyaes::Result::AuthFailed); + ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed); ASSERT_TRUE(pt.empty()); } @@ -92,6 +92,6 @@ TEST(gcm_auth_fail_wrong_iv) std::vector pt; auto result = tinyaes::gcm_decrypt(make_key(), wrong_iv, {}, ct, tag, pt); - ASSERT_TRUE(result == tinyaes::Result::AuthFailed); + ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed); ASSERT_TRUE(pt.empty()); } diff --git a/tests/test_harness.h b/tests/test_harness.h index 4c3efa1..fb1883c 100644 --- a/tests/test_harness.h +++ b/tests/test_harness.h @@ -75,10 +75,10 @@ namespace test { std::fprintf(stderr, "%s:%d: ASSERT_EQ failed\n got: ", file, line); for (auto x : a) - std::fprintf(stderr, "%02x", x); + std::fprintf(stderr, "%02x", static_cast(x)); std::fprintf(stderr, "\n expected: "); for (auto x : b) - std::fprintf(stderr, "%02x", x); + std::fprintf(stderr, "%02x", static_cast(x)); std::fprintf(stderr, "\n"); fail_count()++; } diff --git a/tests/test_padding.cpp b/tests/test_padding.cpp index a7655d4..b2a2b69 100644 --- a/tests/test_padding.cpp +++ b/tests/test_padding.cpp @@ -13,11 +13,11 @@ TEST(pkcs7_full_block_padding) std::vector ct, pt; auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, plaintext); } @@ -29,11 +29,11 @@ TEST(pkcs7_single_byte) std::vector ct, pt; auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_TRUE(ct.size() == 16); // 1 + 15 padding result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, plaintext); } @@ -45,11 +45,11 @@ TEST(pkcs7_empty_plaintext) std::vector ct, pt; auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, plaintext); } @@ -61,11 +61,11 @@ TEST(pkcs7_15_byte_plaintext) std::vector ct, pt; auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); 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_TRUE(result == tinyaes::Result::Ok); ASSERT_EQ(pt, plaintext); } @@ -80,7 +80,7 @@ TEST(pkcs7_invalid_padding_rejected) std::vector ct, pt; auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct); - ASSERT_TRUE(result == tinyaes::Result::Success); + ASSERT_TRUE(result == tinyaes::Result::Ok); // Flip a bit in the last block (padding block) ct.back() ^= 0x01; @@ -88,3 +88,25 @@ TEST(pkcs7_invalid_padding_rejected) result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt); ASSERT_TRUE(result == tinyaes::Result::InvalidPadding); } + +TEST(pkcs7_multi_position_corruption) +{ + // Encrypt a known 16-byte plaintext, then corrupt each of the 16 positions + // in the padding block and verify InvalidPadding for each + std::vector key(16, 0xEE); + std::vector iv(16, 0x00); + std::vector plaintext(16, 0x42); + std::vector ct_orig, pt; + + auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct_orig); + ASSERT_TRUE(result == tinyaes::Result::Ok); + ASSERT_TRUE(ct_orig.size() == 32); // 16 data + 16 padding block + + for (size_t i = 0; i < 16; ++i) + { + std::vector ct = ct_orig; + ct[16 + i] ^= 0x01; // corrupt position i of the padding block + result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt); + ASSERT_TRUE(result == tinyaes::Result::InvalidPadding); + } +} diff --git a/tests/vectors/aes_gcm_vectors.inl b/tests/vectors/aes_gcm_vectors.inl index 001b83e..e86315b 100644 --- a/tests/vectors/aes_gcm_vectors.inl +++ b/tests/vectors/aes_gcm_vectors.inl @@ -79,6 +79,37 @@ 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 7: AES-192, no plaintext, no AAD +static const uint8_t gcm_tc7_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 +}; +static const uint8_t gcm_tc7_iv[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; +// No plaintext, no AAD, no ciphertext +static const uint8_t gcm_tc7_tag[] = { + 0xcd,0x33,0xb2,0x8a,0xc7,0x73,0xf7,0x4b,0xa0,0x0e,0xd1,0xf3,0x12,0x57,0x24,0x35 +}; + +// Test Case 8: AES-192, 16 bytes plaintext, no AAD +static const uint8_t gcm_tc8_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 +}; +static const uint8_t gcm_tc8_iv[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; +static const uint8_t gcm_tc8_plain[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; +static const uint8_t gcm_tc8_cipher[] = { + 0x98,0xe7,0x24,0x7c,0x07,0xf0,0xfe,0x41,0x1c,0x26,0x7e,0x43,0x84,0xb0,0xf6,0x00 +}; +static const uint8_t gcm_tc8_tag[] = { + 0x2f,0xf5,0x8d,0x80,0x03,0x39,0x27,0xab,0x8e,0xf4,0xd4,0x58,0x75,0x14,0xf0,0xfb +}; + // 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,