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.
This commit is contained in:
Brandon Lehmann
2026-02-24 21:57:00 -05:00
parent cc49624c7a
commit b4df5d078a
30 changed files with 1646 additions and 277 deletions

View File

@@ -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
$<$<CONFIG:Debug>:-D_GLIBCXX_ASSERTIONS>
)
@@ -131,10 +133,11 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
$<$<NOT:$<CONFIG:Debug>>:-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 " $<$<CONFIG:Debug>:-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

233
README.md Normal file
View File

@@ -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<uint8_t> key(32, 0x42); // AES-256
std::vector<uint8_t> plaintext = { /* ... */ };
std::vector<uint8_t> aad = { /* associated data */ };
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> key(32, 0x42);
std::vector<uint8_t> plaintext = { /* ... */ };
std::vector<uint8_t> 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<uint8_t> decrypted;
result = tinyaes::ctr_decrypt(key, output, decrypted);
```
**CBC with PKCS#7 padding**:
```cpp
#include "tinyaes/cbc.h"
std::vector<uint8_t> key(16, 0x42); // AES-128
std::vector<uint8_t> plaintext = { /* ... */ };
std::vector<uint8_t> 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<uint8_t> 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).

View File

@@ -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()

View File

@@ -2,84 +2,307 @@
// BSD 3-Clause License (see LICENSE)
#include "tinyaes.h"
#include "internal/aes_impl.h"
#include "internal/ghash.h"
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
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 <intrin.h>
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<uint64_t>(hi) << 32) | lo;
}
#endif
#else
#define HAS_RDTSC 0
#endif
static constexpr int ITERATIONS = 100;
template<typename Fn> static double bench(const char *name, Fn &&fn)
struct BenchResult
{
double mib_per_sec;
double cycles_per_byte; // 0 if rdtsc not available
};
template<typename Fn> 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<double, std::milli>(end - start).count();
double total_bytes = static_cast<double>(BENCH_SIZE) * ITERATIONS;
double total_bytes = static_cast<double>(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<double>(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<typename EncFn>
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<uint8_t> key(kl, 0x42);
std::vector<uint8_t> iv_16(16, 0x01);
std::vector<uint8_t> iv_12(12, 0x01);
std::vector<uint8_t> 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<uint8_t> pt_ecb(ecb_sz, 0x55);
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> ct_pre, tag_pre;
tinyaes::gcm_encrypt(key, iv_12, aad, pt, ct_pre, tag_pre);
bench(
label, sz,
[&]()
{
std::vector<uint8_t> 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<uint8_t> key(16, 0x42);
std::vector<uint8_t> iv(12, 0x01);
std::vector<uint8_t> pt(4096, 0x55);
static const size_t aad_sizes[] = {0, 16, 64, 256, 1024, 4096};
for (size_t aad_sz : aad_sizes)
{
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> key_128(16, 0x42);
std::vector<uint8_t> key_256(32, 0x42);
std::vector<uint8_t> iv_16(16, 0x01);
std::vector<uint8_t> iv_12(12, 0x01);
std::vector<uint8_t> plaintext(BENCH_SIZE, 0x55);
std::vector<uint8_t> aad = {0xAA, 0xBB, 0xCC, 0xDD};
std::printf("TinyAES Benchmarks (%zu bytes x %d iterations)\n", BENCH_SIZE, ITERATIONS);
std::printf("TinyAES Benchmarks (%d iterations per measurement)\n", ITERATIONS);
std::printf("================================================================\n");
// ECB
bench("ECB-128 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::ecb_encrypt(key_128, plaintext, ct);
});
bench("ECB-256 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::ecb_encrypt(key_256, plaintext, ct);
});
// CBC
bench("CBC-128 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::cbc_encrypt(key_128, iv_16, plaintext, ct);
});
bench("CBC-256 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::cbc_encrypt(key_256, iv_16, plaintext, ct);
});
// CTR
bench("CTR-128 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::ctr_crypt(key_128, iv_16, plaintext, ct);
});
bench("CTR-256 encrypt", [&]() {
std::vector<uint8_t> ct;
tinyaes::ctr_crypt(key_256, iv_16, plaintext, ct);
});
// GCM
bench("GCM-128 encrypt", [&]() {
std::vector<uint8_t> ct, tag;
tinyaes::gcm_encrypt(key_128, iv_12, aad, plaintext, ct, tag);
});
bench("GCM-256 encrypt", [&]() {
std::vector<uint8_t> ct, tag;
tinyaes::gcm_encrypt(key_256, iv_12, aad, plaintext, ct, tag);
});
run_backend_comparison();
run_key_expansion_benchmarks();
run_ghash_benchmarks();
run_mode_benchmarks();
run_gcm_aad_benchmarks();
return 0;
}

View File

@@ -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()

View File

@@ -2,34 +2,62 @@
// BSD 3-Clause License (see LICENSE)
#include "tinyaes/cbc.h"
#include "internal/aes_impl.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
// 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<uint8_t> key(data, data + 16);
std::vector<uint8_t> 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<uint8_t> key(data, data + key_len);
std::vector<uint8_t> 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<uint8_t> plaintext(data + 32, data + size);
std::vector<uint8_t> plaintext(data + key_len + 16, data + size);
std::vector<uint8_t> 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;
}

View File

@@ -2,28 +2,69 @@
// BSD 3-Clause License (see LICENSE)
#include "tinyaes/ctr.h"
#include "internal/aes_impl.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
// 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<uint8_t> out_portable(blocks * 16);
std::vector<uint8_t> 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<uint8_t> key(data, data + 16);
std::vector<uint8_t> iv(data + 16, data + 32);
std::vector<uint8_t> 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<uint8_t> key(data, data + key_len);
std::vector<uint8_t> iv(data + key_len, data + key_len + 16);
std::vector<uint8_t> plaintext(data + key_len + 16, data + size);
std::vector<uint8_t> 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;
}

View File

@@ -2,31 +2,72 @@
// BSD 3-Clause License (see LICENSE)
#include "tinyaes/ecb.h"
#include "internal/aes_impl.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
// 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<uint8_t> 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<uint8_t> key(data, data + key_len);
size_t pt_len = ((size - key_len) / 16) * 16;
if (pt_len == 0)
return 0;
std::vector<uint8_t> plaintext(data + 16, data + 16 + pt_len);
std::vector<uint8_t> plaintext(data + key_len, data + key_len + pt_len);
std::vector<uint8_t> 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;
}

View File

@@ -2,34 +2,117 @@
// BSD 3-Clause License (see LICENSE)
#include "tinyaes/gcm.h"
#include "internal/aes_impl.h"
#include "internal/ghash.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
// 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<uint8_t> &key, const std::vector<uint8_t> &iv,
const std::vector<uint8_t> &aad, const std::vector<uint8_t> &ct,
const std::vector<uint8_t> &tag, uint8_t tamper_byte)
{
std::vector<uint8_t> pt;
// Tamper ciphertext (if non-empty)
if (!ct.empty())
{
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> key(data, data + 16);
std::vector<uint8_t> iv(data + 16, data + 28);
size_t aad_len = data[28] % (size - 29 + 1);
if (29 + aad_len > size)
// 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<uint8_t> key(data, data + key_len);
std::vector<uint8_t> 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<uint8_t> aad(data + 29, data + 29 + aad_len);
std::vector<uint8_t> plaintext(data + 29 + aad_len, data + size);
std::vector<uint8_t> aad(data + header, data + header + aad_len);
std::vector<uint8_t> plaintext(data + header + aad_len, data + size);
std::vector<uint8_t> ct, tag, pt;
auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag);
if (result != tinyaes::Result::Success)
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;
}

View File

@@ -106,6 +106,18 @@ namespace tinyaes
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &plaintext);
// CBC encrypt with PKCS#7 — library generates IV, prepended to ciphertext
Result cbc_encrypt_pkcs7(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &iv_and_ciphertext);
// CBC decrypt with PKCS#7 — IV is first 16 bytes of input
Result cbc_decrypt_pkcs7(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv_and_ciphertext,
std::vector<uint8_t> &plaintext);
} // namespace tinyaes
#endif

View File

@@ -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<uint8_t> generate_iv();
std::vector<uint8_t> generate_nonce();
} // namespace tinyaes
#endif

View File

@@ -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<uint8_t> &input,
std::vector<uint8_t> &output);
// CTR encrypt — caller provides nonce (12 bytes, counter starts at 1)
Result ctr_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext);
// CTR encrypt — library generates nonce, prepended to output
Result ctr_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &nonce_and_ciphertext);
// CTR decrypt — caller provides nonce
Result ctr_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &plaintext);
// CTR decrypt — nonce is first 12 bytes of input
Result ctr_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce_and_ciphertext,
std::vector<uint8_t> &plaintext);
} // namespace tinyaes
#endif

View File

@@ -86,6 +86,36 @@ namespace tinyaes
const std::vector<uint8_t> &tag,
std::vector<uint8_t> &plaintext);
// GCM encrypt — caller provides nonce, tag appended to ciphertext
Result gcm_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &plaintext,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &ciphertext_and_tag);
// GCM encrypt — library generates nonce, output is nonce||ciphertext||tag
Result gcm_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &nonce_ciphertext_tag);
// GCM decrypt — caller provides nonce, input is ciphertext||tag
Result gcm_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &ciphertext_and_tag,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &plaintext);
// GCM decrypt — nonce is first 12 bytes of input, rest is ciphertext||tag
Result gcm_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce_ciphertext_tag,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &plaintext);
} // namespace tinyaes
#endif

View File

@@ -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<const uint8_t *>(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<const uint8_t *>(rk);
uint8x16_t block = vld1q_u8(in);
// Decryption uses inverse round keys in reverse order
for (int i = rounds; i > 1; --i)
// 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

View File

@@ -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

View File

@@ -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<uint8_t> padded(plaintext.size() + pad_len);
std::memcpy(padded.data(), plaintext.data(), plaintext.size());
std::memset(padded.data() + plaintext.size(), static_cast<int>(pad_len), pad_len);
std::memset(padded.data() + plaintext.size(), static_cast<unsigned char>(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<uint8_t> 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<uint8_t>(((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<uint8_t>(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<uint8_t>((15 - j - pad_val) >> 8);
bad_pad |= static_cast<uint8_t>(should_be_pad & (last_block[j] ^ pad_val));
}
uint8_t bad = static_cast<uint8_t>(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<ptrdiff_t>(start));
secure_zero(decrypted.data(), decrypted.size());
return Result::Success;
return Result::Ok;
}
Result cbc_encrypt_pkcs7(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &iv_and_ciphertext)
{
auto iv = generate_iv();
if (iv.empty())
return Result::InternalError;
std::vector<uint8_t> 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<uint8_t> &key,
const std::vector<uint8_t> &iv_and_ciphertext,
std::vector<uint8_t> &plaintext)
{
if (iv_and_ciphertext.size() < 32)
return Result::InvalidInputSize;
std::vector<uint8_t> iv(iv_and_ciphertext.begin(), iv_and_ciphertext.begin() + 16);
std::vector<uint8_t> 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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> 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<int>(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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> 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<int>(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<uint8_t> 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<int>(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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> 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<int>(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;
}

View File

@@ -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<uint8_t> nonce_to_iv(const std::vector<uint8_t> &nonce)
{
std::vector<uint8_t> 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<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &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<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &nonce_and_ciphertext)
{
auto nonce = generate_nonce();
if (nonce.empty())
return Result::InternalError;
std::vector<uint8_t> 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<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &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<uint8_t> &key,
const std::vector<uint8_t> &nonce_and_ciphertext,
std::vector<uint8_t> &plaintext)
{
if (nonce_and_ciphertext.size() < 13)
return Result::InvalidInputSize;
std::vector<uint8_t> nonce(nonce_and_ciphertext.begin(), nonce_and_ciphertext.begin() + 12);
std::vector<uint8_t> 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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> 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<int>(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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> n(nonce, nonce + 12);
std::vector<uint8_t> pt(plaintext, plaintext + plaintext_len);
std::vector<uint8_t> 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<int>(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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> n(nonce, nonce + 12);
std::vector<uint8_t> ct(ciphertext, ciphertext + ciphertext_len);
std::vector<uint8_t> 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<int>(result);
}
std::memcpy(plaintext, pt.data(), pt.size());
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_OK;
}

View File

@@ -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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> 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<int>(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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> 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<int>(result);
}
std::memcpy(plaintext, pt.data(), pt.size());
return TINYAES_SUCCESS;
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_OK;
}

View File

@@ -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<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &plaintext,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &ciphertext_and_tag)
{
std::vector<uint8_t> 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<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &nonce_ciphertext_tag)
{
auto nonce = generate_nonce();
if (nonce.empty())
return Result::InternalError;
std::vector<uint8_t> 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<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &ciphertext_and_tag,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &plaintext)
{
if (ciphertext_and_tag.size() < 16)
return Result::InvalidInputSize;
size_t ct_len = ciphertext_and_tag.size() - 16;
std::vector<uint8_t> ct(ciphertext_and_tag.begin(), ciphertext_and_tag.begin() + static_cast<ptrdiff_t>(ct_len));
std::vector<uint8_t> 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<uint8_t> &key,
const std::vector<uint8_t> &nonce_ciphertext_tag,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &plaintext)
{
if (nonce_ciphertext_tag.size() < 28) // 12 nonce + 16 tag minimum
return Result::InvalidInputSize;
std::vector<uint8_t> nonce(nonce_ciphertext_tag.begin(), nonce_ciphertext_tag.begin() + 12);
std::vector<uint8_t> 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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + iv_len);
std::vector<uint8_t> a(aad ? aad : key, aad ? aad + aad_len : key);
if (!aad)
a.clear();
std::vector<uint8_t> pt(plaintext ? plaintext : key, plaintext ? plaintext + plaintext_len : key);
if (!plaintext)
pt.clear();
std::vector<uint8_t> a;
if (aad && aad_len > 0)
a.assign(aad, aad + aad_len);
std::vector<uint8_t> pt;
if (plaintext && plaintext_len > 0)
pt.assign(plaintext, plaintext + plaintext_len);
std::vector<uint8_t> 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<int>(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<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + iv_len);
std::vector<uint8_t> a(aad ? aad : key, aad ? aad + aad_len : key);
if (!aad)
a.clear();
std::vector<uint8_t> ct(ciphertext ? ciphertext : key, ciphertext ? ciphertext + ciphertext_len : key);
if (!ciphertext)
ct.clear();
std::vector<uint8_t> a;
if (aad && aad_len > 0)
a.assign(aad, aad + aad_len);
std::vector<uint8_t> ct;
if (ciphertext && ciphertext_len > 0)
ct.assign(ciphertext, ciphertext + ciphertext_len);
std::vector<uint8_t> t(tag, tag + 16);
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(k, v, a, ct, t, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
{
tinyaes::secure_zero(pt.data(), pt.size());
return static_cast<int>(result);
}
if (!pt.empty())
std::memcpy(plaintext, pt.data(), pt.size());
return TINYAES_SUCCESS;
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_OK;
}

View File

@@ -63,4 +63,30 @@ namespace tinyaes
#endif
}
std::vector<uint8_t> generate_iv()
{
std::vector<uint8_t> iv(16);
if (generate_iv(iv.data(), 16) != 0)
iv.clear();
return iv;
}
std::vector<uint8_t> generate_nonce()
{
std::vector<uint8_t> 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);
}

View File

@@ -68,8 +68,8 @@ namespace tinyaes
{
diff |= static_cast<uint8_t>(a[i] ^ b[i]);
}
volatile uint8_t result = static_cast<uint8_t>(diff == 0);
return result != 0;
uint8_t d = diff; // volatile read is the barrier
return d == 0;
}
} // namespace tinyaes

View File

@@ -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")

View File

@@ -12,7 +12,7 @@ TEST(cbc_aes128_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::cbc_encrypt(VEC(cbc_128_key), VEC(cbc_128_iv), VEC(cbc_128_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(ct, VEC(cbc_128_cipher));
}
@@ -20,7 +20,7 @@ TEST(cbc_aes128_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::cbc_decrypt(VEC(cbc_128_key), VEC(cbc_128_iv), VEC(cbc_128_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(cbc_128_plain));
}
@@ -28,7 +28,7 @@ TEST(cbc_aes192_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::cbc_encrypt(VEC(cbc_192_key), VEC(cbc_192_iv), VEC(cbc_192_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(ct, VEC(cbc_192_cipher));
}
@@ -36,7 +36,7 @@ TEST(cbc_aes192_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::cbc_decrypt(VEC(cbc_192_key), VEC(cbc_192_iv), VEC(cbc_192_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(cbc_192_plain));
}
@@ -44,7 +44,7 @@ TEST(cbc_aes256_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::cbc_encrypt(VEC(cbc_256_key), VEC(cbc_256_iv), VEC(cbc_256_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(ct, VEC(cbc_256_cipher));
}
@@ -52,7 +52,7 @@ TEST(cbc_aes256_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::cbc_decrypt(VEC(cbc_256_key), VEC(cbc_256_iv), VEC(cbc_256_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(cbc_256_plain));
}
@@ -64,18 +64,66 @@ TEST(cbc_roundtrip_pkcs7)
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> key(16, 0x42);
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext(64, 0x55); // 4 blocks
std::vector<uint8_t> 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<uint8_t> key(16, 0x42);
std::vector<uint8_t> plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
std::vector<uint8_t> 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<uint8_t> key(15, 0x42); // invalid: not 16/24/32
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext(16, 0x55);
std::vector<uint8_t> ct;
auto result = tinyaes::cbc_encrypt(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::InvalidKeySize);
}
#undef VEC

View File

@@ -12,7 +12,7 @@ TEST(ctr_aes128_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_crypt(VEC(ctr_128_key), VEC(ctr_128_iv), VEC(ctr_128_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(ct, VEC(ctr_128_cipher));
}
@@ -20,7 +20,7 @@ TEST(ctr_aes128_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::ctr_crypt(VEC(ctr_128_key), VEC(ctr_128_iv), VEC(ctr_128_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(ctr_128_plain));
}
@@ -28,7 +28,7 @@ TEST(ctr_aes192_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_crypt(VEC(ctr_192_key), VEC(ctr_192_iv), VEC(ctr_192_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(ct, VEC(ctr_192_cipher));
}
@@ -36,7 +36,7 @@ TEST(ctr_aes256_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_crypt(VEC(ctr_256_key), VEC(ctr_256_iv), VEC(ctr_256_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(ct, VEC(ctr_256_cipher));
}
@@ -49,12 +49,12 @@ TEST(ctr_partial_block)
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> key(16, 0x42);
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext;
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::InvalidInputSize);
}
TEST(ctr_invalid_nonce_size)
{
std::vector<uint8_t> key(16, 0x42);
std::vector<uint8_t> nonce(10, 0x00); // wrong size
std::vector<uint8_t> plaintext = {0x01, 0x02, 0x03};
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_encrypt(key, nonce, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::InvalidNonceSize);
}
TEST(ctr_nonce_encrypt_decrypt_roundtrip)
{
std::vector<uint8_t> key(16, 0x42);
std::vector<uint8_t> nonce(12, 0x01);
std::vector<uint8_t> plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
std::vector<uint8_t> 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<uint8_t> key(16, 0x42);
std::vector<uint8_t> plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
std::vector<uint8_t> 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<uint8_t> key(15, 0x42); // invalid: not 16/24/32
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext = {0x01, 0x02, 0x03};
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
ASSERT_TRUE(result == tinyaes::Result::InvalidKeySize);
}
#undef VEC

View File

@@ -12,7 +12,7 @@ TEST(ecb_aes128_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ecb_encrypt(VEC(ecb_128_key), VEC(ecb_128_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(ct, VEC(ecb_128_cipher));
}
@@ -20,7 +20,7 @@ TEST(ecb_aes128_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::ecb_decrypt(VEC(ecb_128_key), VEC(ecb_128_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(ecb_128_plain));
}
@@ -28,7 +28,7 @@ TEST(ecb_aes192_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ecb_encrypt(VEC(ecb_192_key), VEC(ecb_192_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(ct, VEC(ecb_192_cipher));
}
@@ -36,7 +36,7 @@ TEST(ecb_aes192_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::ecb_decrypt(VEC(ecb_192_key), VEC(ecb_192_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(ecb_192_plain));
}
@@ -44,7 +44,7 @@ TEST(ecb_aes256_encrypt)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ecb_encrypt(VEC(ecb_256_key), VEC(ecb_256_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(ct, VEC(ecb_256_cipher));
}
@@ -52,7 +52,7 @@ TEST(ecb_aes256_decrypt)
{
std::vector<uint8_t> pt;
auto result = tinyaes::ecb_decrypt(VEC(ecb_256_key), VEC(ecb_256_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(ecb_256_plain));
}
@@ -60,7 +60,7 @@ TEST(ecb_aes128_multi_block)
{
std::vector<uint8_t> ct;
auto result = tinyaes::ecb_encrypt(VEC(ecb_128_multi_key), VEC(ecb_128_multi_plain), ct);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_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<uint8_t> pt;
auto result = tinyaes::ecb_decrypt(VEC(ecb_128_multi_key), VEC(ecb_128_multi_cipher), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_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<uint8_t> 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<uint8_t> 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

View File

@@ -13,7 +13,7 @@ TEST(gcm_tc1_aes128_no_plaintext_no_aad)
{
std::vector<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc1_key), VEC(gcm_tc1_iv), EMPTY_VEC, EMPTY_VEC, ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(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<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc2_key), VEC(gcm_tc2_iv), EMPTY_VEC, VEC(gcm_tc2_plain), ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_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<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc3_key), VEC(gcm_tc3_iv), EMPTY_VEC, VEC(gcm_tc3_plain), ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_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<uint8_t> ct, tag;
auto result =
tinyaes::gcm_encrypt(VEC(gcm_tc4_key), VEC(gcm_tc4_iv), VEC(gcm_tc4_aad), VEC(gcm_tc4_plain), ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_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<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc13_key), VEC(gcm_tc13_iv), EMPTY_VEC, EMPTY_VEC, ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(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<uint8_t> ct, tag;
auto result = tinyaes::gcm_encrypt(VEC(gcm_tc14_key), VEC(gcm_tc14_iv), EMPTY_VEC, VEC(gcm_tc14_plain), ct, tag);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_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<uint8_t> pt;
auto result =
tinyaes::gcm_decrypt(VEC(gcm_tc2_key), VEC(gcm_tc2_iv), EMPTY_VEC, VEC(gcm_tc2_cipher), VEC(gcm_tc2_tag), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(gcm_tc2_plain));
}
@@ -78,7 +78,7 @@ TEST(gcm_tc3_decrypt_verify)
std::vector<uint8_t> pt;
auto result =
tinyaes::gcm_decrypt(VEC(gcm_tc3_key), VEC(gcm_tc3_iv), EMPTY_VEC, VEC(gcm_tc3_cipher), VEC(gcm_tc3_tag), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(gcm_tc3_plain));
}
@@ -87,7 +87,7 @@ TEST(gcm_tc4_decrypt_verify)
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(VEC(gcm_tc4_key), VEC(gcm_tc4_iv), VEC(gcm_tc4_aad), VEC(gcm_tc4_cipher),
VEC(gcm_tc4_tag), pt);
ASSERT_TRUE(result == tinyaes::Result::Success);
ASSERT_TRUE(result == tinyaes::Result::Ok);
ASSERT_EQ(pt, VEC(gcm_tc4_plain));
}
@@ -100,12 +100,128 @@ TEST(gcm_roundtrip)
std::vector<uint8_t> 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<uint8_t> key(16, 0x42);
std::vector<uint8_t> iv; // empty
std::vector<uint8_t> plaintext = {0x01};
std::vector<uint8_t> 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<uint8_t> key(16, 0x42);
std::vector<uint8_t> nonce(12, 0x01);
std::vector<uint8_t> aad = {0xAA, 0xBB};
std::vector<uint8_t> plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
std::vector<uint8_t> 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<uint8_t> key(16, 0x42);
std::vector<uint8_t> aad = {0xCC};
std::vector<uint8_t> plaintext = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
std::vector<uint8_t> 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<uint8_t> key(15, 0x42); // invalid: not 16/24/32
std::vector<uint8_t> iv(12, 0x01);
std::vector<uint8_t> plaintext = {0x01};
std::vector<uint8_t> 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<uint8_t> key(16, 0x42);
std::vector<uint8_t> iv(12, 0x01);
std::vector<uint8_t> aad = {0xAA, 0xBB, 0xCC, 0xDD};
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> bad_tag = VEC(gcm_tc8_tag);
bad_tag[0] ^= 0x01;
std::vector<uint8_t> 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

View File

@@ -35,7 +35,7 @@ TEST(gcm_auth_fail_tampered_ciphertext)
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), {}, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed);
ASSERT_TRUE(pt.empty());
}
@@ -49,7 +49,7 @@ TEST(gcm_auth_fail_tampered_tag)
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), {}, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed);
ASSERT_TRUE(pt.empty());
}
@@ -64,7 +64,7 @@ TEST(gcm_auth_fail_tampered_aad)
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), bad_aad, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed);
ASSERT_TRUE(pt.empty());
}
@@ -78,7 +78,7 @@ TEST(gcm_auth_fail_wrong_key)
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(wrong_key, make_iv(), {}, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed);
ASSERT_TRUE(pt.empty());
}
@@ -92,6 +92,6 @@ TEST(gcm_auth_fail_wrong_iv)
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(make_key(), wrong_iv, {}, ct, tag, pt);
ASSERT_TRUE(result == tinyaes::Result::AuthFailed);
ASSERT_TRUE(result == tinyaes::Result::AuthenticationFailed);
ASSERT_TRUE(pt.empty());
}

View File

@@ -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<unsigned>(x));
std::fprintf(stderr, "\n expected: ");
for (auto x : b)
std::fprintf(stderr, "%02x", x);
std::fprintf(stderr, "%02x", static_cast<unsigned>(x));
std::fprintf(stderr, "\n");
fail_count()++;
}

View File

@@ -13,11 +13,11 @@ TEST(pkcs7_full_block_padding)
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> key(16, 0xEE);
std::vector<uint8_t> iv(16, 0x00);
std::vector<uint8_t> plaintext(16, 0x42);
std::vector<uint8_t> 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<uint8_t> 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);
}
}

View File

@@ -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,