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:
@@ -96,6 +96,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|||||||
-fno-strict-overflow
|
-fno-strict-overflow
|
||||||
)
|
)
|
||||||
# Stack protector: requires libssp or compiler-rt at link time.
|
# Stack protector: requires libssp or compiler-rt at link time.
|
||||||
|
# Probe with try_compile to catch Clang+MinGW toolchains that lack libssp.
|
||||||
include(CheckCXXSourceCompiles)
|
include(CheckCXXSourceCompiles)
|
||||||
set(CMAKE_REQUIRED_FLAGS "-fstack-protector-strong")
|
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)
|
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
|
# -fPIC is implied on Windows PE; only needed on ELF/Mach-O
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
target_compile_options(tinyaes PRIVATE -fPIC)
|
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")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
target_compile_options(tinyaes PRIVATE -fstack-clash-protection)
|
target_compile_options(tinyaes PRIVATE -fstack-clash-protection)
|
||||||
endif()
|
endif()
|
||||||
@@ -118,9 +119,10 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|||||||
target_compile_options(tinyaes PRIVATE -fcf-protection=full)
|
target_compile_options(tinyaes PRIVATE -fcf-protection=full)
|
||||||
endif()
|
endif()
|
||||||
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")
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
target_compile_options(tinyaes PRIVATE -Wstrict-aliasing=2)
|
target_compile_options(tinyaes PRIVATE -Wstrict-aliasing=2)
|
||||||
|
# libstdc++ container assertions in Debug (bounds checks on operator[], etc.)
|
||||||
target_compile_options(tinyaes PRIVATE
|
target_compile_options(tinyaes PRIVATE
|
||||||
$<$<CONFIG:Debug>:-D_GLIBCXX_ASSERTIONS>
|
$<$<CONFIG:Debug>:-D_GLIBCXX_ASSERTIONS>
|
||||||
)
|
)
|
||||||
@@ -131,10 +133,11 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|||||||
$<$<NOT:$<CONFIG:Debug>>:-D_FORTIFY_SOURCE=2>
|
$<$<NOT:$<CONFIG:Debug>>:-D_FORTIFY_SOURCE=2>
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
# Linker hardening
|
# Linker hardening (use LINK_FLAGS for CMake 3.10 compat)
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS
|
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS
|
||||||
" -Wl,-z,relro,-z,now -Wl,-z,noexecstack")
|
" -Wl,-z,relro,-z,now -Wl,-z,noexecstack")
|
||||||
|
# Reject undefined symbols in shared libs (catches missing TINYAES_EXPORT)
|
||||||
if(BUILD_SHARED_LIBS)
|
if(BUILD_SHARED_LIBS)
|
||||||
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-z,defs")
|
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-z,defs")
|
||||||
endif()
|
endif()
|
||||||
@@ -158,6 +161,7 @@ elseif(MSVC)
|
|||||||
endif()
|
endif()
|
||||||
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS
|
set_property(TARGET tinyaes APPEND_STRING PROPERTY LINK_FLAGS
|
||||||
" /DYNAMICBASE /NXCOMPAT /HIGHENTROPYVA")
|
" /DYNAMICBASE /NXCOMPAT /HIGHENTROPYVA")
|
||||||
|
# CET shadow stack compatibility
|
||||||
check_cxx_compiler_flag("/guard:ehcont" HAS_GUARD_EHCONT)
|
check_cxx_compiler_flag("/guard:ehcont" HAS_GUARD_EHCONT)
|
||||||
if(HAS_GUARD_EHCONT)
|
if(HAS_GUARD_EHCONT)
|
||||||
target_compile_options(tinyaes PRIVATE /guard:ehcont)
|
target_compile_options(tinyaes PRIVATE /guard:ehcont)
|
||||||
@@ -166,16 +170,26 @@ elseif(MSVC)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# --- Per-backend SIMD compile flags ---
|
# --- 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_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64" AND NOT FORCE_PORTABLE)
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
set_source_files_properties(src/backend/aes_aesni.cpp PROPERTIES
|
set_source_files_properties(src/backend/aes_aesni.cpp PROPERTIES
|
||||||
COMPILE_FLAGS "-maes -msse4.1")
|
COMPILE_FLAGS "-maes -msse4.1")
|
||||||
set_source_files_properties(src/backend/aes_vaes.cpp PROPERTIES
|
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
|
set_source_files_properties(src/backend/ghash_pclmulqdq.cpp PROPERTIES
|
||||||
COMPILE_FLAGS "-mpclmul -msse4.1")
|
COMPILE_FLAGS "-mpclmul -msse4.1")
|
||||||
set_source_files_properties(src/backend/ghash_vpclmulqdq.cpp PROPERTIES
|
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)
|
elseif(MSVC)
|
||||||
# MSVC: AES-NI and PCLMULQDQ available without extra flags on x64
|
# MSVC: AES-NI and PCLMULQDQ available without extra flags on x64
|
||||||
set_source_files_properties(src/backend/aes_vaes.cpp PROPERTIES
|
set_source_files_properties(src/backend/aes_vaes.cpp PROPERTIES
|
||||||
|
|||||||
233
README.md
Normal file
233
README.md
Normal 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).
|
||||||
@@ -13,8 +13,20 @@ set_target_properties(tinyaes_benchmarks PROPERTIES
|
|||||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Warning flags for benchmarks
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
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)
|
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()
|
endif()
|
||||||
|
|||||||
@@ -2,84 +2,307 @@
|
|||||||
// BSD 3-Clause License (see LICENSE)
|
// BSD 3-Clause License (see LICENSE)
|
||||||
|
|
||||||
#include "tinyaes.h"
|
#include "tinyaes.h"
|
||||||
|
#include "internal/aes_impl.h"
|
||||||
|
#include "internal/ghash.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <vector>
|
#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;
|
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
|
// Warmup
|
||||||
fn();
|
fn();
|
||||||
|
|
||||||
|
#if HAS_RDTSC
|
||||||
|
uint64_t tsc_start = rdtsc();
|
||||||
|
#endif
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
for (int i = 0; i < ITERATIONS; ++i)
|
for (int i = 0; i < ITERATIONS; ++i)
|
||||||
{
|
{
|
||||||
fn();
|
fn();
|
||||||
}
|
}
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
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 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);
|
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()
|
int main()
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> key_128(16, 0x42);
|
std::printf("TinyAES Benchmarks (%d iterations per measurement)\n", ITERATIONS);
|
||||||
std::vector<uint8_t> key_256(32, 0x42);
|
|
||||||
std::vector<uint8_t> iv_16(16, 0x01);
|
|
||||||
std::vector<uint8_t> iv_12(12, 0x01);
|
|
||||||
std::vector<uint8_t> plaintext(BENCH_SIZE, 0x55);
|
|
||||||
std::vector<uint8_t> aad = {0xAA, 0xBB, 0xCC, 0xDD};
|
|
||||||
|
|
||||||
std::printf("TinyAES Benchmarks (%zu bytes x %d iterations)\n", BENCH_SIZE, ITERATIONS);
|
|
||||||
std::printf("================================================================\n");
|
std::printf("================================================================\n");
|
||||||
|
|
||||||
// ECB
|
run_backend_comparison();
|
||||||
bench("ECB-128 encrypt", [&]() {
|
run_key_expansion_benchmarks();
|
||||||
std::vector<uint8_t> ct;
|
run_ghash_benchmarks();
|
||||||
tinyaes::ecb_encrypt(key_128, plaintext, ct);
|
run_mode_benchmarks();
|
||||||
});
|
run_gcm_aad_benchmarks();
|
||||||
bench("ECB-256 encrypt", [&]() {
|
|
||||||
std::vector<uint8_t> ct;
|
|
||||||
tinyaes::ecb_encrypt(key_256, plaintext, ct);
|
|
||||||
});
|
|
||||||
|
|
||||||
// CBC
|
|
||||||
bench("CBC-128 encrypt", [&]() {
|
|
||||||
std::vector<uint8_t> ct;
|
|
||||||
tinyaes::cbc_encrypt(key_128, iv_16, plaintext, ct);
|
|
||||||
});
|
|
||||||
bench("CBC-256 encrypt", [&]() {
|
|
||||||
std::vector<uint8_t> ct;
|
|
||||||
tinyaes::cbc_encrypt(key_256, iv_16, plaintext, ct);
|
|
||||||
});
|
|
||||||
|
|
||||||
// CTR
|
|
||||||
bench("CTR-128 encrypt", [&]() {
|
|
||||||
std::vector<uint8_t> ct;
|
|
||||||
tinyaes::ctr_crypt(key_128, iv_16, plaintext, ct);
|
|
||||||
});
|
|
||||||
bench("CTR-256 encrypt", [&]() {
|
|
||||||
std::vector<uint8_t> ct;
|
|
||||||
tinyaes::ctr_crypt(key_256, iv_16, plaintext, ct);
|
|
||||||
});
|
|
||||||
|
|
||||||
// GCM
|
|
||||||
bench("GCM-128 encrypt", [&]() {
|
|
||||||
std::vector<uint8_t> ct, tag;
|
|
||||||
tinyaes::gcm_encrypt(key_128, iv_12, aad, plaintext, ct, tag);
|
|
||||||
});
|
|
||||||
bench("GCM-256 encrypt", [&]() {
|
|
||||||
std::vector<uint8_t> ct, tag;
|
|
||||||
tinyaes::gcm_encrypt(key_256, iv_12, aad, plaintext, ct, tag);
|
|
||||||
});
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
return()
|
return()
|
||||||
endif()
|
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")
|
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
@@ -19,6 +23,13 @@ foreach(target ${FUZZ_TARGETS})
|
|||||||
CXX_EXTENSIONS OFF
|
CXX_EXTENSIONS OFF
|
||||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/fuzz
|
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")
|
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()
|
endforeach()
|
||||||
|
|||||||
@@ -2,34 +2,62 @@
|
|||||||
// BSD 3-Clause License (see LICENSE)
|
// BSD 3-Clause License (see LICENSE)
|
||||||
|
|
||||||
#include "tinyaes/cbc.h"
|
#include "tinyaes/cbc.h"
|
||||||
|
#include "internal/aes_impl.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#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)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||||
{
|
{
|
||||||
if (size < 32)
|
if (size < 2)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// First 16 bytes = key, next 16 bytes = IV, rest = plaintext
|
// First byte selects key size: 16, 24, or 32
|
||||||
std::vector<uint8_t> key(data, data + 16);
|
static const size_t key_sizes[] = {16, 24, 32};
|
||||||
std::vector<uint8_t> iv(data + 16, data + 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)
|
if (remaining == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Use PKCS#7 which accepts any length
|
std::vector<uint8_t> plaintext(data + key_len + 16, data + size);
|
||||||
std::vector<uint8_t> plaintext(data + 32, data + size);
|
|
||||||
std::vector<uint8_t> ct, pt;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
|
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
|
||||||
if (result != tinyaes::Result::Success)
|
if (result != tinyaes::Result::Ok)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
||||||
assert(result == tinyaes::Result::Success);
|
assert(result == tinyaes::Result::Ok);
|
||||||
assert(pt == plaintext);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,69 @@
|
|||||||
// BSD 3-Clause License (see LICENSE)
|
// BSD 3-Clause License (see LICENSE)
|
||||||
|
|
||||||
#include "tinyaes/ctr.h"
|
#include "tinyaes/ctr.h"
|
||||||
|
#include "internal/aes_impl.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#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)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||||
{
|
{
|
||||||
if (size < 33)
|
if (size < 2)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// First 16 bytes = key, next 16 bytes = IV, rest = plaintext
|
// First byte selects key size: 16, 24, or 32
|
||||||
std::vector<uint8_t> key(data, data + 16);
|
static const size_t key_sizes[] = {16, 24, 32};
|
||||||
std::vector<uint8_t> iv(data + 16, data + 32);
|
size_t key_len = key_sizes[data[0] % 3];
|
||||||
std::vector<uint8_t> plaintext(data + 32, data + size);
|
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;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
|
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
|
||||||
if (result != tinyaes::Result::Success)
|
if (result != tinyaes::Result::Ok)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
result = tinyaes::ctr_crypt(key, iv, ct, pt);
|
result = tinyaes::ctr_crypt(key, iv, ct, pt);
|
||||||
assert(result == tinyaes::Result::Success);
|
assert(result == tinyaes::Result::Ok);
|
||||||
assert(pt == plaintext);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,72 @@
|
|||||||
// BSD 3-Clause License (see LICENSE)
|
// BSD 3-Clause License (see LICENSE)
|
||||||
|
|
||||||
#include "tinyaes/ecb.h"
|
#include "tinyaes/ecb.h"
|
||||||
|
#include "internal/aes_impl.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#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)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||||
{
|
{
|
||||||
if (size < 16)
|
if (size < 2)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Use first 16 bytes as key, rest as plaintext (block-aligned)
|
// First byte selects key size: 16, 24, or 32
|
||||||
std::vector<uint8_t> key(data, data + 16);
|
static const size_t key_sizes[] = {16, 24, 32};
|
||||||
size_t pt_len = ((size - 16) / 16) * 16;
|
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)
|
if (pt_len == 0)
|
||||||
return 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;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::ecb_encrypt(key, plaintext, ct);
|
auto result = tinyaes::ecb_encrypt(key, plaintext, ct);
|
||||||
if (result != tinyaes::Result::Success)
|
if (result != tinyaes::Result::Ok)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
result = tinyaes::ecb_decrypt(key, ct, pt);
|
result = tinyaes::ecb_decrypt(key, ct, pt);
|
||||||
assert(result == tinyaes::Result::Success);
|
assert(result == tinyaes::Result::Ok);
|
||||||
assert(pt == plaintext);
|
assert(pt == plaintext);
|
||||||
|
|
||||||
|
// Differential test: portable vs dispatched
|
||||||
|
diff_encrypt_block(data, key_len, data + key_len, pt_len);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,34 +2,117 @@
|
|||||||
// BSD 3-Clause License (see LICENSE)
|
// BSD 3-Clause License (see LICENSE)
|
||||||
|
|
||||||
#include "tinyaes/gcm.h"
|
#include "tinyaes/gcm.h"
|
||||||
|
#include "internal/aes_impl.h"
|
||||||
|
#include "internal/ghash.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#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)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||||
{
|
{
|
||||||
if (size < 29)
|
if (size < 2)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// First 16 bytes = key, next 12 bytes = IV, 1 byte = AAD length, rest split
|
// First byte selects key size: 16, 24, or 32
|
||||||
std::vector<uint8_t> key(data, data + 16);
|
static const size_t key_sizes[] = {16, 24, 32};
|
||||||
std::vector<uint8_t> iv(data + 16, data + 28);
|
size_t key_len = key_sizes[data[0] % 3];
|
||||||
size_t aad_len = data[28] % (size - 29 + 1);
|
data++;
|
||||||
if (29 + aad_len > size)
|
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;
|
aad_len = 0;
|
||||||
|
|
||||||
std::vector<uint8_t> aad(data + 29, data + 29 + aad_len);
|
std::vector<uint8_t> aad(data + header, data + header + aad_len);
|
||||||
std::vector<uint8_t> plaintext(data + 29 + aad_len, data + size);
|
std::vector<uint8_t> plaintext(data + header + aad_len, data + size);
|
||||||
|
|
||||||
std::vector<uint8_t> ct, tag, pt;
|
std::vector<uint8_t> ct, tag, pt;
|
||||||
|
|
||||||
auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag);
|
auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag);
|
||||||
if (result != tinyaes::Result::Success)
|
if (result != tinyaes::Result::Ok)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
result = tinyaes::gcm_decrypt(key, iv, aad, ct, tag, pt);
|
result = tinyaes::gcm_decrypt(key, iv, aad, ct, tag, pt);
|
||||||
assert(result == tinyaes::Result::Success);
|
assert(result == tinyaes::Result::Ok);
|
||||||
assert(pt == plaintext);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,18 @@ namespace tinyaes
|
|||||||
const std::vector<uint8_t> &ciphertext,
|
const std::vector<uint8_t> &ciphertext,
|
||||||
std::vector<uint8_t> &plaintext);
|
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
|
} // namespace tinyaes
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -56,12 +56,14 @@
|
|||||||
#define TINYAES_GCM_IV_SIZE 12
|
#define TINYAES_GCM_IV_SIZE 12
|
||||||
|
|
||||||
// C error codes
|
// C error codes
|
||||||
#define TINYAES_SUCCESS 0
|
#define TINYAES_OK 0
|
||||||
#define TINYAES_ERROR_INVALID_KEY_SIZE (-1)
|
#define TINYAES_INVALID_KEY_SIZE (-1)
|
||||||
#define TINYAES_ERROR_INVALID_INPUT (-2)
|
#define TINYAES_INVALID_IV_SIZE (-2)
|
||||||
#define TINYAES_ERROR_INVALID_PADDING (-3)
|
#define TINYAES_INVALID_NONCE_SIZE (-3)
|
||||||
#define TINYAES_ERROR_AUTH_FAILED (-4)
|
#define TINYAES_INVALID_INPUT_SIZE (-4)
|
||||||
#define TINYAES_ERROR_BUFFER_TOO_SMALL (-5)
|
#define TINYAES_INVALID_PADDING (-5)
|
||||||
|
#define TINYAES_AUTH_FAILED (-6)
|
||||||
|
#define TINYAES_INTERNAL_ERROR (-7)
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C"
|
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_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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -81,12 +87,14 @@ namespace tinyaes
|
|||||||
|
|
||||||
enum class Result
|
enum class Result
|
||||||
{
|
{
|
||||||
Success = 0,
|
Ok = 0,
|
||||||
InvalidKeySize = -1,
|
InvalidKeySize = -1,
|
||||||
InvalidInput = -2,
|
InvalidIVSize = -2,
|
||||||
InvalidPadding = -3,
|
InvalidNonceSize = -3,
|
||||||
AuthFailed = -4,
|
InvalidInputSize = -4,
|
||||||
BufferTooSmall = -5
|
InvalidPadding = -5,
|
||||||
|
AuthenticationFailed = -6,
|
||||||
|
InternalError = -7
|
||||||
};
|
};
|
||||||
|
|
||||||
void secure_zero(void *ptr, size_t len);
|
void secure_zero(void *ptr, size_t len);
|
||||||
@@ -102,6 +110,10 @@ namespace tinyaes
|
|||||||
|
|
||||||
int generate_iv(uint8_t *out, size_t len);
|
int generate_iv(uint8_t *out, size_t len);
|
||||||
|
|
||||||
|
std::vector<uint8_t> generate_iv();
|
||||||
|
|
||||||
|
std::vector<uint8_t> generate_nonce();
|
||||||
|
|
||||||
} // namespace tinyaes
|
} // namespace tinyaes
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ extern "C"
|
|||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// CTR encrypt/decrypt (symmetric operation)
|
// CTR encrypt/decrypt (symmetric operation) — raw 16-byte IV
|
||||||
TINYAES_EXPORT int tinyaes_ctr_crypt(
|
TINYAES_EXPORT int tinyaes_ctr_crypt(
|
||||||
const uint8_t *key,
|
const uint8_t *key,
|
||||||
size_t key_len,
|
size_t key_len,
|
||||||
@@ -43,6 +43,26 @@ extern "C"
|
|||||||
uint8_t *output,
|
uint8_t *output,
|
||||||
size_t output_len);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -61,6 +81,32 @@ namespace tinyaes
|
|||||||
const std::vector<uint8_t> &input,
|
const std::vector<uint8_t> &input,
|
||||||
std::vector<uint8_t> &output);
|
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
|
} // namespace tinyaes
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -86,6 +86,36 @@ namespace tinyaes
|
|||||||
const std::vector<uint8_t> &tag,
|
const std::vector<uint8_t> &tag,
|
||||||
std::vector<uint8_t> &plaintext);
|
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
|
} // namespace tinyaes
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ namespace tinyaes
|
|||||||
// Note: ARM AES instructions combine SubBytes+ShiftRows (vaese) and MixColumns (vaesmc)
|
// Note: ARM AES instructions combine SubBytes+ShiftRows (vaese) and MixColumns (vaesmc)
|
||||||
// The key XOR is done separately (veorq).
|
// 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])
|
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);
|
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
|
// So we need: vaese(block, key[i]) then vaesmcq for MixColumns
|
||||||
for (int i = 0; i < rounds - 1; ++i)
|
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 = vaeseq_u8(block, key);
|
||||||
block = vaesmcq_u8(block);
|
block = vaesmcq_u8(block);
|
||||||
}
|
}
|
||||||
// Last round: no MixColumns
|
// 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);
|
block = vaeseq_u8(block, key_last);
|
||||||
// Final AddRoundKey
|
// 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);
|
block = veorq_u8(block, key_final);
|
||||||
|
|
||||||
vst1q_u8(out, block);
|
vst1q_u8(out, block);
|
||||||
@@ -76,19 +84,22 @@ namespace tinyaes
|
|||||||
const uint8_t *rk8 = reinterpret_cast<const uint8_t *>(rk);
|
const uint8_t *rk8 = reinterpret_cast<const uint8_t *>(rk);
|
||||||
uint8x16_t block = vld1q_u8(in);
|
uint8x16_t block = vld1q_u8(in);
|
||||||
|
|
||||||
// Decryption uses inverse round keys in reverse order
|
// ARM AESD places AddRoundKey before InvSubBytes, but standard AES
|
||||||
for (int i = rounds; i > 1; --i)
|
// 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, vaesimcq_u8(load_rk(rk8 + i * 16)));
|
||||||
block = vaesdq_u8(block, key);
|
|
||||||
block = vaesimcq_u8(block);
|
block = vaesimcq_u8(block);
|
||||||
}
|
}
|
||||||
// Last round: no InvMixColumns
|
// Last round: key needs InvMixColumns, but no InvMixColumns on state
|
||||||
uint8x16_t key_1 = vld1q_u8(rk8 + 16);
|
block = vaesdq_u8(block, vaesimcq_u8(load_rk(rk8 + 16)));
|
||||||
block = vaesdq_u8(block, key_1);
|
|
||||||
// Final AddRoundKey
|
// Final AddRoundKey
|
||||||
uint8x16_t key_0 = vld1q_u8(rk8);
|
block = veorq_u8(block, load_rk(rk8));
|
||||||
block = veorq_u8(block, key_0);
|
|
||||||
|
|
||||||
vst1q_u8(out, block);
|
vst1q_u8(out, block);
|
||||||
}
|
}
|
||||||
@@ -104,13 +115,13 @@ namespace tinyaes
|
|||||||
|
|
||||||
for (int r = 0; r < rounds - 1; ++r)
|
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 = vaeseq_u8(block, key);
|
||||||
block = vaesmcq_u8(block);
|
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);
|
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);
|
block = veorq_u8(block, key_final);
|
||||||
|
|
||||||
// XOR with plaintext
|
// XOR with plaintext
|
||||||
|
|||||||
@@ -44,15 +44,16 @@ namespace tinyaes
|
|||||||
{
|
{
|
||||||
|
|
||||||
// GF(2^128) multiplication using PMULL (carry-less multiply)
|
// 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)
|
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_lo = vgetq_lane_p64(vreinterpretq_p64_u8(a), 0);
|
||||||
poly64_t a_hi = vgetq_lane_p64(vreinterpretq_p64_u8(a), 1);
|
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_lo = vgetq_lane_p64(vreinterpretq_p64_u8(b), 0);
|
||||||
poly64_t b_hi = vgetq_lane_p64(vreinterpretq_p64_u8(b), 1);
|
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 lo = vmull_p64(a_lo, b_lo);
|
||||||
poly128_t hi = vmull_p64(a_hi, b_hi);
|
poly128_t hi = vmull_p64(a_hi, b_hi);
|
||||||
poly128_t mid0 = vmull_p64(a_lo, 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 hi_v = vreinterpretq_u8_p128(hi);
|
||||||
uint8x16_t mid = veorq_u8(vreinterpretq_u8_p128(mid0), vreinterpretq_u8_p128(mid1));
|
uint8x16_t mid = veorq_u8(vreinterpretq_u8_p128(mid0), vreinterpretq_u8_p128(mid1));
|
||||||
|
|
||||||
// Combine: shift mid and XOR into lo/hi
|
// Combine mid*x^64 into [hi_v : lo_v]
|
||||||
uint8x16_t mid_lo = vextq_u8(vdupq_n_u8(0), mid, 8);
|
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);
|
uint8x16_t mid_hi = vextq_u8(mid, vdupq_n_u8(0), 8); // mid >> 64
|
||||||
lo_v = veorq_u8(lo_v, mid_lo);
|
lo_v = veorq_u8(lo_v, mid_lo);
|
||||||
hi_v = veorq_u8(hi_v, mid_hi);
|
hi_v = veorq_u8(hi_v, mid_hi);
|
||||||
|
|
||||||
// Reduction modulo x^128 + x^7 + x^2 + x + 1
|
// 256-bit product = [D3:D2:D1:D0] where hi_v=[D3:D2], lo_v=[D1:D0]
|
||||||
// Using the reflected reduction polynomial 0xC2...01
|
// Reduction modulo p(x) = x^128 + x^7 + x^2 + x + 1
|
||||||
poly64_t r = (poly64_t)0xC200000000000000ULL;
|
// Since x^128 ≡ x^7+x^2+x+1, let q = 0x87
|
||||||
poly128_t t1 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(hi_v), 0), r);
|
poly64_t q = (poly64_t)0x87ULL;
|
||||||
uint8x16_t f = veorq_u8(lo_v, vreinterpretq_u8_p128(t1));
|
|
||||||
uint8x16_t f_swap = vextq_u8(f, f, 8);
|
|
||||||
poly128_t t2 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(f_swap), 0), r);
|
|
||||||
|
|
||||||
return veorq_u8(veorq_u8(f_swap, vreinterpretq_u8_p128(t2)), hi_v);
|
// 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])
|
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
|
// GCM uses MSB-first bit ordering (bit 7 of byte 0 = x^0), but ARM PMULL
|
||||||
// The byte reversal handles the endianness conversion
|
// uses LSB-first (bit 0 = x^0). vrbitq_u8 reverses bits within each byte
|
||||||
uint8x16_t rev_mask = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
|
// to convert between these conventions.
|
||||||
uint8x16_t h = vqtbl1q_u8(vld1q_u8(H), rev_mask);
|
uint8x16_t h = vrbitq_u8(vld1q_u8(H));
|
||||||
uint8x16_t y = vqtbl1q_u8(vld1q_u8(Y), rev_mask);
|
uint8x16_t y = vrbitq_u8(vld1q_u8(Y));
|
||||||
|
|
||||||
size_t full_blocks = data_len / 16;
|
size_t full_blocks = data_len / 16;
|
||||||
for (size_t i = 0; i < full_blocks; ++i)
|
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 = veorq_u8(y, d);
|
||||||
y = gf128_mul_pmull(y, h);
|
y = gf128_mul_pmull(y, h);
|
||||||
}
|
}
|
||||||
@@ -100,12 +108,12 @@ namespace tinyaes
|
|||||||
{
|
{
|
||||||
uint8_t padded[16] = {0};
|
uint8_t padded[16] = {0};
|
||||||
std::memcpy(padded, data + full_blocks * 16, remainder);
|
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 = veorq_u8(y, d);
|
||||||
y = gf128_mul_pmull(y, h);
|
y = gf128_mul_pmull(y, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
vst1q_u8(Y, vqtbl1q_u8(y, rev_mask));
|
vst1q_u8(Y, vrbitq_u8(y));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|||||||
112
src/cbc.cpp
112
src/cbc.cpp
@@ -42,9 +42,9 @@ namespace tinyaes
|
|||||||
if (rounds == 0)
|
if (rounds == 0)
|
||||||
return Result::InvalidKeySize;
|
return Result::InvalidKeySize;
|
||||||
if (iv.size() != 16)
|
if (iv.size() != 16)
|
||||||
return Result::InvalidInput;
|
return Result::InvalidIVSize;
|
||||||
if (plaintext.empty() || (plaintext.size() % 16) != 0)
|
if (plaintext.empty() || (plaintext.size() % 16) != 0)
|
||||||
return Result::InvalidInput;
|
return Result::InvalidInputSize;
|
||||||
|
|
||||||
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
||||||
auto key_expand = internal::get_key_expand();
|
auto key_expand = internal::get_key_expand();
|
||||||
@@ -69,7 +69,7 @@ namespace tinyaes
|
|||||||
|
|
||||||
secure_zero(rk, sizeof(rk));
|
secure_zero(rk, sizeof(rk));
|
||||||
secure_zero(block, sizeof(block));
|
secure_zero(block, sizeof(block));
|
||||||
return Result::Success;
|
return Result::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result cbc_decrypt(
|
Result cbc_decrypt(
|
||||||
@@ -82,9 +82,9 @@ namespace tinyaes
|
|||||||
if (rounds == 0)
|
if (rounds == 0)
|
||||||
return Result::InvalidKeySize;
|
return Result::InvalidKeySize;
|
||||||
if (iv.size() != 16)
|
if (iv.size() != 16)
|
||||||
return Result::InvalidInput;
|
return Result::InvalidIVSize;
|
||||||
if (ciphertext.empty() || (ciphertext.size() % 16) != 0)
|
if (ciphertext.empty() || (ciphertext.size() % 16) != 0)
|
||||||
return Result::InvalidInput;
|
return Result::InvalidInputSize;
|
||||||
|
|
||||||
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
||||||
auto key_expand = internal::get_key_expand();
|
auto key_expand = internal::get_key_expand();
|
||||||
@@ -107,7 +107,7 @@ namespace tinyaes
|
|||||||
}
|
}
|
||||||
|
|
||||||
secure_zero(rk, sizeof(rk));
|
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)
|
// 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);
|
size_t pad_len = 16 - (plaintext.size() % 16);
|
||||||
std::vector<uint8_t> padded(plaintext.size() + pad_len);
|
std::vector<uint8_t> padded(plaintext.size() + pad_len);
|
||||||
std::memcpy(padded.data(), plaintext.data(), plaintext.size());
|
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);
|
auto result = cbc_encrypt(key, iv, padded, ciphertext);
|
||||||
secure_zero(padded.data(), padded.size());
|
secure_zero(padded.data(), padded.size());
|
||||||
@@ -137,34 +137,74 @@ namespace tinyaes
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> decrypted;
|
std::vector<uint8_t> decrypted;
|
||||||
auto result = cbc_decrypt(key, iv, ciphertext, decrypted);
|
auto result = cbc_decrypt(key, iv, ciphertext, decrypted);
|
||||||
if (result != Result::Success)
|
if (result != Result::Ok)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
if (decrypted.empty())
|
if (decrypted.empty())
|
||||||
return Result::InvalidPadding;
|
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();
|
uint8_t pad_val = decrypted.back();
|
||||||
if (pad_val == 0 || pad_val > 16)
|
|
||||||
return Result::InvalidPadding;
|
|
||||||
|
|
||||||
// Verify all padding bytes match (constant-time)
|
// Range check via arithmetic (no branches):
|
||||||
volatile uint8_t bad = 0;
|
// bad_range is 0xFF if pad_val is out of [1..16], 0x00 otherwise
|
||||||
size_t start = decrypted.size() - pad_val;
|
uint8_t bad_range = static_cast<uint8_t>(((pad_val - 1) >> 8) | ((16 - pad_val) >> 8));
|
||||||
for (size_t i = start; i < decrypted.size(); ++i)
|
|
||||||
|
// 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)
|
if (bad != 0)
|
||||||
{
|
{
|
||||||
secure_zero(decrypted.data(), decrypted.size());
|
secure_zero(decrypted.data(), decrypted.size());
|
||||||
return Result::InvalidPadding;
|
return Result::InvalidPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t start = decrypted.size() - pad_val;
|
||||||
plaintext.assign(decrypted.begin(), decrypted.begin() + static_cast<ptrdiff_t>(start));
|
plaintext.assign(decrypted.begin(), decrypted.begin() + static_cast<ptrdiff_t>(start));
|
||||||
secure_zero(decrypted.data(), decrypted.size());
|
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
|
} // namespace tinyaes
|
||||||
@@ -180,9 +220,9 @@ extern "C" int tinyaes_cbc_encrypt(
|
|||||||
size_t ciphertext_len)
|
size_t ciphertext_len)
|
||||||
{
|
{
|
||||||
if (!key || !iv || !plaintext || !ciphertext)
|
if (!key || !iv || !plaintext || !ciphertext)
|
||||||
return TINYAES_ERROR_INVALID_INPUT;
|
return TINYAES_INVALID_INPUT_SIZE;
|
||||||
if (ciphertext_len < plaintext_len)
|
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> k(key, key + key_len);
|
||||||
std::vector<uint8_t> v(iv, iv + 16);
|
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);
|
auto result = tinyaes::cbc_encrypt(k, v, pt, ct);
|
||||||
tinyaes::secure_zero(k.data(), k.size());
|
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);
|
return static_cast<int>(result);
|
||||||
|
|
||||||
std::memcpy(ciphertext, ct.data(), ct.size());
|
std::memcpy(ciphertext, ct.data(), ct.size());
|
||||||
return TINYAES_SUCCESS;
|
return TINYAES_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" int tinyaes_cbc_decrypt(
|
extern "C" int tinyaes_cbc_decrypt(
|
||||||
@@ -209,9 +250,9 @@ extern "C" int tinyaes_cbc_decrypt(
|
|||||||
size_t plaintext_len)
|
size_t plaintext_len)
|
||||||
{
|
{
|
||||||
if (!key || !iv || !ciphertext || !plaintext)
|
if (!key || !iv || !ciphertext || !plaintext)
|
||||||
return TINYAES_ERROR_INVALID_INPUT;
|
return TINYAES_INVALID_INPUT_SIZE;
|
||||||
if (plaintext_len < ciphertext_len)
|
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> k(key, key + key_len);
|
||||||
std::vector<uint8_t> v(iv, iv + 16);
|
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);
|
auto result = tinyaes::cbc_decrypt(k, v, ct, pt);
|
||||||
tinyaes::secure_zero(k.data(), k.size());
|
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);
|
return static_cast<int>(result);
|
||||||
|
}
|
||||||
|
|
||||||
std::memcpy(plaintext, pt.data(), pt.size());
|
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(
|
extern "C" int tinyaes_cbc_encrypt_pkcs7(
|
||||||
@@ -238,13 +283,13 @@ extern "C" int tinyaes_cbc_encrypt_pkcs7(
|
|||||||
size_t *ciphertext_len)
|
size_t *ciphertext_len)
|
||||||
{
|
{
|
||||||
if (!key || !iv || !plaintext || !ciphertext || !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));
|
size_t padded_len = plaintext_len + (16 - (plaintext_len % 16));
|
||||||
if (*ciphertext_len < padded_len)
|
if (*ciphertext_len < padded_len)
|
||||||
{
|
{
|
||||||
*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);
|
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);
|
auto result = tinyaes::cbc_encrypt_pkcs7(k, v, pt, ct);
|
||||||
tinyaes::secure_zero(k.data(), k.size());
|
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);
|
return static_cast<int>(result);
|
||||||
|
|
||||||
std::memcpy(ciphertext, ct.data(), ct.size());
|
std::memcpy(ciphertext, ct.data(), ct.size());
|
||||||
*ciphertext_len = ct.size();
|
*ciphertext_len = ct.size();
|
||||||
return TINYAES_SUCCESS;
|
return TINYAES_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" int tinyaes_cbc_decrypt_pkcs7(
|
extern "C" int tinyaes_cbc_decrypt_pkcs7(
|
||||||
@@ -273,7 +319,7 @@ extern "C" int tinyaes_cbc_decrypt_pkcs7(
|
|||||||
size_t *plaintext_len)
|
size_t *plaintext_len)
|
||||||
{
|
{
|
||||||
if (!key || !iv || !ciphertext || !plaintext || !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> k(key, key + key_len);
|
||||||
std::vector<uint8_t> v(iv, iv + 16);
|
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);
|
auto result = tinyaes::cbc_decrypt_pkcs7(k, v, ct, pt);
|
||||||
tinyaes::secure_zero(k.data(), k.size());
|
tinyaes::secure_zero(k.data(), k.size());
|
||||||
|
|
||||||
if (result != tinyaes::Result::Success)
|
if (result != tinyaes::Result::Ok)
|
||||||
return static_cast<int>(result);
|
return static_cast<int>(result);
|
||||||
|
|
||||||
if (*plaintext_len < pt.size())
|
if (*plaintext_len < pt.size())
|
||||||
{
|
{
|
||||||
*plaintext_len = pt.size();
|
*plaintext_len = pt.size();
|
||||||
tinyaes::secure_zero(pt.data(), 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());
|
std::memcpy(plaintext, pt.data(), pt.size());
|
||||||
*plaintext_len = pt.size();
|
*plaintext_len = pt.size();
|
||||||
tinyaes::secure_zero(pt.data(), pt.size());
|
tinyaes::secure_zero(pt.data(), pt.size());
|
||||||
return TINYAES_SUCCESS;
|
return TINYAES_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
144
src/ctr.cpp
144
src/ctr.cpp
@@ -43,9 +43,9 @@ namespace tinyaes
|
|||||||
if (rounds == 0)
|
if (rounds == 0)
|
||||||
return Result::InvalidKeySize;
|
return Result::InvalidKeySize;
|
||||||
if (iv.size() != 16)
|
if (iv.size() != 16)
|
||||||
return Result::InvalidInput;
|
return Result::InvalidIVSize;
|
||||||
if (input.empty())
|
if (input.empty())
|
||||||
return Result::InvalidInput;
|
return Result::InvalidInputSize;
|
||||||
|
|
||||||
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
||||||
auto key_expand = internal::get_key_expand();
|
auto key_expand = internal::get_key_expand();
|
||||||
@@ -83,7 +83,73 @@ namespace tinyaes
|
|||||||
|
|
||||||
secure_zero(rk, sizeof(rk));
|
secure_zero(rk, sizeof(rk));
|
||||||
secure_zero(ctr, sizeof(ctr));
|
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
|
} // namespace tinyaes
|
||||||
@@ -98,9 +164,9 @@ extern "C" int tinyaes_ctr_crypt(
|
|||||||
size_t output_len)
|
size_t output_len)
|
||||||
{
|
{
|
||||||
if (!key || !iv || !input || !output)
|
if (!key || !iv || !input || !output)
|
||||||
return TINYAES_ERROR_INVALID_INPUT;
|
return TINYAES_INVALID_INPUT_SIZE;
|
||||||
if (output_len < input_len)
|
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> k(key, key + key_len);
|
||||||
std::vector<uint8_t> v(iv, iv + 16);
|
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);
|
auto result = tinyaes::ctr_crypt(k, v, in, out);
|
||||||
tinyaes::secure_zero(k.data(), k.size());
|
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);
|
return static_cast<int>(result);
|
||||||
|
|
||||||
std::memcpy(output, out.data(), out.size());
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/ecb.cpp
29
src/ecb.cpp
@@ -41,7 +41,7 @@ namespace tinyaes
|
|||||||
if (rounds == 0)
|
if (rounds == 0)
|
||||||
return Result::InvalidKeySize;
|
return Result::InvalidKeySize;
|
||||||
if (plaintext.empty() || (plaintext.size() % 16) != 0)
|
if (plaintext.empty() || (plaintext.size() % 16) != 0)
|
||||||
return Result::InvalidInput;
|
return Result::InvalidInputSize;
|
||||||
|
|
||||||
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
||||||
auto key_expand = internal::get_key_expand();
|
auto key_expand = internal::get_key_expand();
|
||||||
@@ -56,7 +56,7 @@ namespace tinyaes
|
|||||||
}
|
}
|
||||||
|
|
||||||
secure_zero(rk, sizeof(rk));
|
secure_zero(rk, sizeof(rk));
|
||||||
return Result::Success;
|
return Result::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ecb_decrypt(
|
Result ecb_decrypt(
|
||||||
@@ -68,7 +68,7 @@ namespace tinyaes
|
|||||||
if (rounds == 0)
|
if (rounds == 0)
|
||||||
return Result::InvalidKeySize;
|
return Result::InvalidKeySize;
|
||||||
if (ciphertext.empty() || (ciphertext.size() % 16) != 0)
|
if (ciphertext.empty() || (ciphertext.size() % 16) != 0)
|
||||||
return Result::InvalidInput;
|
return Result::InvalidInputSize;
|
||||||
|
|
||||||
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
||||||
auto key_expand = internal::get_key_expand();
|
auto key_expand = internal::get_key_expand();
|
||||||
@@ -83,7 +83,7 @@ namespace tinyaes
|
|||||||
}
|
}
|
||||||
|
|
||||||
secure_zero(rk, sizeof(rk));
|
secure_zero(rk, sizeof(rk));
|
||||||
return Result::Success;
|
return Result::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace tinyaes
|
} // namespace tinyaes
|
||||||
@@ -97,9 +97,9 @@ extern "C" int tinyaes_ecb_encrypt(
|
|||||||
size_t ciphertext_len)
|
size_t ciphertext_len)
|
||||||
{
|
{
|
||||||
if (!key || !plaintext || !ciphertext)
|
if (!key || !plaintext || !ciphertext)
|
||||||
return TINYAES_ERROR_INVALID_INPUT;
|
return TINYAES_INVALID_INPUT_SIZE;
|
||||||
if (ciphertext_len < plaintext_len)
|
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> k(key, key + key_len);
|
||||||
std::vector<uint8_t> pt(plaintext, plaintext + plaintext_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);
|
auto result = tinyaes::ecb_encrypt(k, pt, ct);
|
||||||
tinyaes::secure_zero(k.data(), k.size());
|
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);
|
return static_cast<int>(result);
|
||||||
|
|
||||||
std::memcpy(ciphertext, ct.data(), ct.size());
|
std::memcpy(ciphertext, ct.data(), ct.size());
|
||||||
return TINYAES_SUCCESS;
|
return TINYAES_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" int tinyaes_ecb_decrypt(
|
extern "C" int tinyaes_ecb_decrypt(
|
||||||
@@ -124,9 +125,9 @@ extern "C" int tinyaes_ecb_decrypt(
|
|||||||
size_t plaintext_len)
|
size_t plaintext_len)
|
||||||
{
|
{
|
||||||
if (!key || !ciphertext || !plaintext)
|
if (!key || !ciphertext || !plaintext)
|
||||||
return TINYAES_ERROR_INVALID_INPUT;
|
return TINYAES_INVALID_INPUT_SIZE;
|
||||||
if (plaintext_len < ciphertext_len)
|
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> k(key, key + key_len);
|
||||||
std::vector<uint8_t> ct(ciphertext, ciphertext + ciphertext_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);
|
auto result = tinyaes::ecb_decrypt(k, ct, pt);
|
||||||
tinyaes::secure_zero(k.data(), k.size());
|
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);
|
return static_cast<int>(result);
|
||||||
|
}
|
||||||
|
|
||||||
std::memcpy(plaintext, pt.data(), pt.size());
|
std::memcpy(plaintext, pt.data(), pt.size());
|
||||||
return TINYAES_SUCCESS;
|
tinyaes::secure_zero(pt.data(), pt.size());
|
||||||
|
return TINYAES_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
131
src/gcm.cpp
131
src/gcm.cpp
@@ -123,7 +123,7 @@ namespace tinyaes
|
|||||||
if (rounds == 0)
|
if (rounds == 0)
|
||||||
return Result::InvalidKeySize;
|
return Result::InvalidKeySize;
|
||||||
if (iv.empty())
|
if (iv.empty())
|
||||||
return Result::InvalidInput;
|
return Result::InvalidIVSize;
|
||||||
|
|
||||||
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
||||||
auto key_expand = internal::get_key_expand();
|
auto key_expand = internal::get_key_expand();
|
||||||
@@ -180,7 +180,7 @@ namespace tinyaes
|
|||||||
secure_zero(rk, sizeof(rk));
|
secure_zero(rk, sizeof(rk));
|
||||||
secure_zero(H, sizeof(H));
|
secure_zero(H, sizeof(H));
|
||||||
secure_zero(J0, sizeof(J0));
|
secure_zero(J0, sizeof(J0));
|
||||||
return Result::Success;
|
return Result::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result gcm_decrypt(
|
Result gcm_decrypt(
|
||||||
@@ -195,9 +195,9 @@ namespace tinyaes
|
|||||||
if (rounds == 0)
|
if (rounds == 0)
|
||||||
return Result::InvalidKeySize;
|
return Result::InvalidKeySize;
|
||||||
if (iv.empty())
|
if (iv.empty())
|
||||||
return Result::InvalidInput;
|
return Result::InvalidIVSize;
|
||||||
if (tag.size() != 16)
|
if (tag.size() != 16)
|
||||||
return Result::InvalidInput;
|
return Result::InvalidInputSize;
|
||||||
|
|
||||||
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
uint32_t rk[internal::AES_MAX_RK_WORDS];
|
||||||
auto key_expand = internal::get_key_expand();
|
auto key_expand = internal::get_key_expand();
|
||||||
@@ -226,7 +226,7 @@ namespace tinyaes
|
|||||||
secure_zero(H, sizeof(H));
|
secure_zero(H, sizeof(H));
|
||||||
secure_zero(J0, sizeof(J0));
|
secure_zero(J0, sizeof(J0));
|
||||||
secure_zero(computed_tag, sizeof(computed_tag));
|
secure_zero(computed_tag, sizeof(computed_tag));
|
||||||
return Result::AuthFailed;
|
return Result::AuthenticationFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag verified — decrypt ciphertext using CTR mode
|
// Tag verified — decrypt ciphertext using CTR mode
|
||||||
@@ -264,7 +264,77 @@ namespace tinyaes
|
|||||||
secure_zero(H, sizeof(H));
|
secure_zero(H, sizeof(H));
|
||||||
secure_zero(J0, sizeof(J0));
|
secure_zero(J0, sizeof(J0));
|
||||||
secure_zero(computed_tag, sizeof(computed_tag));
|
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
|
} // namespace tinyaes
|
||||||
@@ -283,32 +353,33 @@ extern "C" int tinyaes_gcm_encrypt(
|
|||||||
uint8_t tag[16])
|
uint8_t tag[16])
|
||||||
{
|
{
|
||||||
if (!key || !iv || !tag)
|
if (!key || !iv || !tag)
|
||||||
return TINYAES_ERROR_INVALID_INPUT;
|
return TINYAES_INVALID_INPUT_SIZE;
|
||||||
if (plaintext_len > 0 && (!plaintext || !ciphertext))
|
if (plaintext_len > 0 && (!plaintext || !ciphertext))
|
||||||
return TINYAES_ERROR_INVALID_INPUT;
|
return TINYAES_INVALID_INPUT_SIZE;
|
||||||
if (ciphertext_len < plaintext_len)
|
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> k(key, key + key_len);
|
||||||
std::vector<uint8_t> v(iv, iv + iv_len);
|
std::vector<uint8_t> v(iv, iv + iv_len);
|
||||||
std::vector<uint8_t> a(aad ? aad : key, aad ? aad + aad_len : key);
|
std::vector<uint8_t> a;
|
||||||
if (!aad)
|
if (aad && aad_len > 0)
|
||||||
a.clear();
|
a.assign(aad, aad + aad_len);
|
||||||
std::vector<uint8_t> pt(plaintext ? plaintext : key, plaintext ? plaintext + plaintext_len : key);
|
std::vector<uint8_t> pt;
|
||||||
if (!plaintext)
|
if (plaintext && plaintext_len > 0)
|
||||||
pt.clear();
|
pt.assign(plaintext, plaintext + plaintext_len);
|
||||||
std::vector<uint8_t> ct, t;
|
std::vector<uint8_t> ct, t;
|
||||||
|
|
||||||
auto result = tinyaes::gcm_encrypt(k, v, a, pt, ct, t);
|
auto result = tinyaes::gcm_encrypt(k, v, a, pt, ct, t);
|
||||||
tinyaes::secure_zero(k.data(), k.size());
|
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);
|
return static_cast<int>(result);
|
||||||
|
|
||||||
if (!ct.empty())
|
if (!ct.empty())
|
||||||
std::memcpy(ciphertext, ct.data(), ct.size());
|
std::memcpy(ciphertext, ct.data(), ct.size());
|
||||||
std::memcpy(tag, t.data(), 16);
|
std::memcpy(tag, t.data(), 16);
|
||||||
return TINYAES_SUCCESS;
|
return TINYAES_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" int tinyaes_gcm_decrypt(
|
extern "C" int tinyaes_gcm_decrypt(
|
||||||
@@ -325,30 +396,34 @@ extern "C" int tinyaes_gcm_decrypt(
|
|||||||
const uint8_t tag[16])
|
const uint8_t tag[16])
|
||||||
{
|
{
|
||||||
if (!key || !iv || !tag)
|
if (!key || !iv || !tag)
|
||||||
return TINYAES_ERROR_INVALID_INPUT;
|
return TINYAES_INVALID_INPUT_SIZE;
|
||||||
if (ciphertext_len > 0 && (!ciphertext || !plaintext))
|
if (ciphertext_len > 0 && (!ciphertext || !plaintext))
|
||||||
return TINYAES_ERROR_INVALID_INPUT;
|
return TINYAES_INVALID_INPUT_SIZE;
|
||||||
if (plaintext_len < ciphertext_len)
|
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> k(key, key + key_len);
|
||||||
std::vector<uint8_t> v(iv, iv + iv_len);
|
std::vector<uint8_t> v(iv, iv + iv_len);
|
||||||
std::vector<uint8_t> a(aad ? aad : key, aad ? aad + aad_len : key);
|
std::vector<uint8_t> a;
|
||||||
if (!aad)
|
if (aad && aad_len > 0)
|
||||||
a.clear();
|
a.assign(aad, aad + aad_len);
|
||||||
std::vector<uint8_t> ct(ciphertext ? ciphertext : key, ciphertext ? ciphertext + ciphertext_len : key);
|
std::vector<uint8_t> ct;
|
||||||
if (!ciphertext)
|
if (ciphertext && ciphertext_len > 0)
|
||||||
ct.clear();
|
ct.assign(ciphertext, ciphertext + ciphertext_len);
|
||||||
std::vector<uint8_t> t(tag, tag + 16);
|
std::vector<uint8_t> t(tag, tag + 16);
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
|
|
||||||
auto result = tinyaes::gcm_decrypt(k, v, a, ct, t, pt);
|
auto result = tinyaes::gcm_decrypt(k, v, a, ct, t, pt);
|
||||||
tinyaes::secure_zero(k.data(), k.size());
|
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);
|
return static_cast<int>(result);
|
||||||
|
}
|
||||||
|
|
||||||
if (!pt.empty())
|
if (!pt.empty())
|
||||||
std::memcpy(plaintext, pt.data(), pt.size());
|
std::memcpy(plaintext, pt.data(), pt.size());
|
||||||
return TINYAES_SUCCESS;
|
tinyaes::secure_zero(pt.data(), pt.size());
|
||||||
|
return TINYAES_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,4 +63,30 @@ namespace tinyaes
|
|||||||
#endif
|
#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
|
} // 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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ namespace tinyaes
|
|||||||
{
|
{
|
||||||
diff |= static_cast<uint8_t>(a[i] ^ b[i]);
|
diff |= static_cast<uint8_t>(a[i] ^ b[i]);
|
||||||
}
|
}
|
||||||
volatile uint8_t result = static_cast<uint8_t>(diff == 0);
|
uint8_t d = diff; // volatile read is the barrier
|
||||||
return result != 0;
|
return d == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace tinyaes
|
} // namespace tinyaes
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|||||||
set_property(TARGET tinyaes_tests APPEND_STRING PROPERTY LINK_FLAGS
|
set_property(TARGET tinyaes_tests APPEND_STRING PROPERTY LINK_FLAGS
|
||||||
" -Wl,-z,relro,-z,now -Wl,-z,noexecstack")
|
" -Wl,-z,relro,-z,now -Wl,-z,noexecstack")
|
||||||
endif()
|
endif()
|
||||||
|
# macOS: -bind_at_load is deprecated on modern macOS (eager binding is the default)
|
||||||
if(MINGW)
|
if(MINGW)
|
||||||
set_property(TARGET tinyaes_tests APPEND_STRING PROPERTY LINK_FLAGS
|
set_property(TARGET tinyaes_tests APPEND_STRING PROPERTY LINK_FLAGS
|
||||||
" -Wl,--nxcompat -Wl,--dynamicbase -Wl,--high-entropy-va")
|
" -Wl,--nxcompat -Wl,--dynamicbase -Wl,--high-entropy-va")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ TEST(cbc_aes128_encrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::cbc_encrypt(VEC(cbc_128_key), VEC(cbc_128_iv), VEC(cbc_128_plain), 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));
|
ASSERT_EQ(ct, VEC(cbc_128_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ TEST(cbc_aes128_decrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::cbc_decrypt(VEC(cbc_128_key), VEC(cbc_128_iv), VEC(cbc_128_cipher), 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));
|
ASSERT_EQ(pt, VEC(cbc_128_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ TEST(cbc_aes192_encrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::cbc_encrypt(VEC(cbc_192_key), VEC(cbc_192_iv), VEC(cbc_192_plain), 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));
|
ASSERT_EQ(ct, VEC(cbc_192_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ TEST(cbc_aes192_decrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::cbc_decrypt(VEC(cbc_192_key), VEC(cbc_192_iv), VEC(cbc_192_cipher), 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));
|
ASSERT_EQ(pt, VEC(cbc_192_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ TEST(cbc_aes256_encrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::cbc_encrypt(VEC(cbc_256_key), VEC(cbc_256_iv), VEC(cbc_256_plain), 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));
|
ASSERT_EQ(ct, VEC(cbc_256_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ TEST(cbc_aes256_decrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::cbc_decrypt(VEC(cbc_256_key), VEC(cbc_256_iv), VEC(cbc_256_cipher), 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));
|
ASSERT_EQ(pt, VEC(cbc_256_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,18 +64,66 @@ TEST(cbc_roundtrip_pkcs7)
|
|||||||
std::vector<uint8_t> ct, pt;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
|
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
|
ASSERT_TRUE(ct.size() == 16); // 5 bytes + 11 padding = 16
|
||||||
|
|
||||||
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
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);
|
ASSERT_EQ(pt, plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(cbc_invalid_iv_size)
|
TEST(cbc_invalid_iv_size)
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> key(16, 0), iv(15, 0), pt(16, 0), ct;
|
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
|
#undef VEC
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ TEST(ctr_aes128_encrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::ctr_crypt(VEC(ctr_128_key), VEC(ctr_128_iv), VEC(ctr_128_plain), 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));
|
ASSERT_EQ(ct, VEC(ctr_128_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ TEST(ctr_aes128_decrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::ctr_crypt(VEC(ctr_128_key), VEC(ctr_128_iv), VEC(ctr_128_cipher), 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));
|
ASSERT_EQ(pt, VEC(ctr_128_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ TEST(ctr_aes192_encrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::ctr_crypt(VEC(ctr_192_key), VEC(ctr_192_iv), VEC(ctr_192_plain), 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));
|
ASSERT_EQ(ct, VEC(ctr_192_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ TEST(ctr_aes256_encrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::ctr_crypt(VEC(ctr_256_key), VEC(ctr_256_iv), VEC(ctr_256_plain), 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));
|
ASSERT_EQ(ct, VEC(ctr_256_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,12 +49,12 @@ TEST(ctr_partial_block)
|
|||||||
std::vector<uint8_t> ct, pt;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
|
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);
|
ASSERT_TRUE(ct.size() == 7);
|
||||||
|
|
||||||
// Decrypt should recover original
|
// Decrypt should recover original
|
||||||
result = tinyaes::ctr_crypt(key, iv, ct, pt);
|
result = tinyaes::ctr_crypt(key, iv, ct, pt);
|
||||||
ASSERT_TRUE(result == tinyaes::Result::Success);
|
ASSERT_TRUE(result == tinyaes::Result::Ok);
|
||||||
ASSERT_EQ(pt, plaintext);
|
ASSERT_EQ(pt, plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,12 +66,76 @@ TEST(ctr_roundtrip_multi_block)
|
|||||||
std::vector<uint8_t> ct, pt;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::ctr_crypt(key, iv, plaintext, ct);
|
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);
|
ASSERT_TRUE(ct.size() == 100);
|
||||||
|
|
||||||
result = tinyaes::ctr_crypt(key, iv, ct, pt);
|
result = tinyaes::ctr_crypt(key, iv, ct, pt);
|
||||||
ASSERT_TRUE(result == tinyaes::Result::Success);
|
ASSERT_TRUE(result == tinyaes::Result::Ok);
|
||||||
ASSERT_EQ(pt, plaintext);
|
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
|
#undef VEC
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ TEST(ecb_aes128_encrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::ecb_encrypt(VEC(ecb_128_key), VEC(ecb_128_plain), 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));
|
ASSERT_EQ(ct, VEC(ecb_128_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ TEST(ecb_aes128_decrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::ecb_decrypt(VEC(ecb_128_key), VEC(ecb_128_cipher), 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));
|
ASSERT_EQ(pt, VEC(ecb_128_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ TEST(ecb_aes192_encrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::ecb_encrypt(VEC(ecb_192_key), VEC(ecb_192_plain), 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));
|
ASSERT_EQ(ct, VEC(ecb_192_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ TEST(ecb_aes192_decrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::ecb_decrypt(VEC(ecb_192_key), VEC(ecb_192_cipher), 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));
|
ASSERT_EQ(pt, VEC(ecb_192_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ TEST(ecb_aes256_encrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::ecb_encrypt(VEC(ecb_256_key), VEC(ecb_256_plain), 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));
|
ASSERT_EQ(ct, VEC(ecb_256_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ TEST(ecb_aes256_decrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::ecb_decrypt(VEC(ecb_256_key), VEC(ecb_256_cipher), 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));
|
ASSERT_EQ(pt, VEC(ecb_256_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ TEST(ecb_aes128_multi_block)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct;
|
std::vector<uint8_t> ct;
|
||||||
auto result = tinyaes::ecb_encrypt(VEC(ecb_128_multi_key), VEC(ecb_128_multi_plain), 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));
|
ASSERT_EQ(ct, VEC(ecb_128_multi_cipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ TEST(ecb_aes128_multi_block_decrypt)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::ecb_decrypt(VEC(ecb_128_multi_key), VEC(ecb_128_multi_cipher), 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));
|
ASSERT_EQ(pt, VEC(ecb_128_multi_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,13 +81,13 @@ TEST(ecb_invalid_key_size)
|
|||||||
TEST(ecb_non_block_aligned)
|
TEST(ecb_non_block_aligned)
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> key(16, 0), pt(17, 0), ct;
|
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)
|
TEST(ecb_empty_input)
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> key(16, 0), pt, ct;
|
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
|
#undef VEC
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ TEST(gcm_tc1_aes128_no_plaintext_no_aad)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct, tag;
|
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);
|
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_TRUE(ct.empty());
|
||||||
ASSERT_EQ(tag, VEC(gcm_tc1_tag));
|
ASSERT_EQ(tag, VEC(gcm_tc1_tag));
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ TEST(gcm_tc2_aes128_16byte_plaintext)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct, tag;
|
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);
|
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(ct, VEC(gcm_tc2_cipher));
|
||||||
ASSERT_EQ(tag, VEC(gcm_tc2_tag));
|
ASSERT_EQ(tag, VEC(gcm_tc2_tag));
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ TEST(gcm_tc3_aes128_64byte_plaintext)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct, tag;
|
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);
|
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(ct, VEC(gcm_tc3_cipher));
|
||||||
ASSERT_EQ(tag, VEC(gcm_tc3_tag));
|
ASSERT_EQ(tag, VEC(gcm_tc3_tag));
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ TEST(gcm_tc4_aes128_with_aad)
|
|||||||
std::vector<uint8_t> ct, tag;
|
std::vector<uint8_t> ct, tag;
|
||||||
auto result =
|
auto result =
|
||||||
tinyaes::gcm_encrypt(VEC(gcm_tc4_key), VEC(gcm_tc4_iv), VEC(gcm_tc4_aad), VEC(gcm_tc4_plain), ct, tag);
|
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(ct, VEC(gcm_tc4_cipher));
|
||||||
ASSERT_EQ(tag, VEC(gcm_tc4_tag));
|
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;
|
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);
|
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_TRUE(ct.empty());
|
||||||
ASSERT_EQ(tag, VEC(gcm_tc13_tag));
|
ASSERT_EQ(tag, VEC(gcm_tc13_tag));
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ TEST(gcm_tc14_aes256_16byte_plaintext)
|
|||||||
{
|
{
|
||||||
std::vector<uint8_t> ct, tag;
|
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);
|
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(ct, VEC(gcm_tc14_cipher));
|
||||||
ASSERT_EQ(tag, VEC(gcm_tc14_tag));
|
ASSERT_EQ(tag, VEC(gcm_tc14_tag));
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ TEST(gcm_tc2_decrypt_verify)
|
|||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result =
|
auto result =
|
||||||
tinyaes::gcm_decrypt(VEC(gcm_tc2_key), VEC(gcm_tc2_iv), EMPTY_VEC, VEC(gcm_tc2_cipher), VEC(gcm_tc2_tag), pt);
|
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));
|
ASSERT_EQ(pt, VEC(gcm_tc2_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ TEST(gcm_tc3_decrypt_verify)
|
|||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result =
|
auto result =
|
||||||
tinyaes::gcm_decrypt(VEC(gcm_tc3_key), VEC(gcm_tc3_iv), EMPTY_VEC, VEC(gcm_tc3_cipher), VEC(gcm_tc3_tag), pt);
|
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));
|
ASSERT_EQ(pt, VEC(gcm_tc3_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ TEST(gcm_tc4_decrypt_verify)
|
|||||||
std::vector<uint8_t> pt;
|
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),
|
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);
|
VEC(gcm_tc4_tag), pt);
|
||||||
ASSERT_TRUE(result == tinyaes::Result::Success);
|
ASSERT_TRUE(result == tinyaes::Result::Ok);
|
||||||
ASSERT_EQ(pt, VEC(gcm_tc4_plain));
|
ASSERT_EQ(pt, VEC(gcm_tc4_plain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,12 +100,128 @@ TEST(gcm_roundtrip)
|
|||||||
std::vector<uint8_t> ct, tag, pt;
|
std::vector<uint8_t> ct, tag, pt;
|
||||||
|
|
||||||
auto result = tinyaes::gcm_encrypt(key, iv, aad, plaintext, ct, tag);
|
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);
|
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);
|
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 VEC
|
||||||
#undef EMPTY_VEC
|
#undef EMPTY_VEC
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ TEST(gcm_auth_fail_tampered_ciphertext)
|
|||||||
|
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), {}, ct, tag, 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());
|
ASSERT_TRUE(pt.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ TEST(gcm_auth_fail_tampered_tag)
|
|||||||
|
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), {}, ct, tag, 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());
|
ASSERT_TRUE(pt.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ TEST(gcm_auth_fail_tampered_aad)
|
|||||||
|
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::gcm_decrypt(make_key(), make_iv(), bad_aad, ct, tag, 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());
|
ASSERT_TRUE(pt.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ TEST(gcm_auth_fail_wrong_key)
|
|||||||
|
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::gcm_decrypt(wrong_key, make_iv(), {}, ct, tag, 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());
|
ASSERT_TRUE(pt.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +92,6 @@ TEST(gcm_auth_fail_wrong_iv)
|
|||||||
|
|
||||||
std::vector<uint8_t> pt;
|
std::vector<uint8_t> pt;
|
||||||
auto result = tinyaes::gcm_decrypt(make_key(), wrong_iv, {}, ct, tag, 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());
|
ASSERT_TRUE(pt.empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,10 +75,10 @@ namespace test
|
|||||||
{
|
{
|
||||||
std::fprintf(stderr, "%s:%d: ASSERT_EQ failed\n got: ", file, line);
|
std::fprintf(stderr, "%s:%d: ASSERT_EQ failed\n got: ", file, line);
|
||||||
for (auto x : a)
|
for (auto x : a)
|
||||||
std::fprintf(stderr, "%02x", x);
|
std::fprintf(stderr, "%02x", static_cast<unsigned>(x));
|
||||||
std::fprintf(stderr, "\n expected: ");
|
std::fprintf(stderr, "\n expected: ");
|
||||||
for (auto x : b)
|
for (auto x : b)
|
||||||
std::fprintf(stderr, "%02x", x);
|
std::fprintf(stderr, "%02x", static_cast<unsigned>(x));
|
||||||
std::fprintf(stderr, "\n");
|
std::fprintf(stderr, "\n");
|
||||||
fail_count()++;
|
fail_count()++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ TEST(pkcs7_full_block_padding)
|
|||||||
std::vector<uint8_t> ct, pt;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
|
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
|
ASSERT_TRUE(ct.size() == 32); // 16 data + 16 padding
|
||||||
|
|
||||||
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
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);
|
ASSERT_EQ(pt, plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,11 +29,11 @@ TEST(pkcs7_single_byte)
|
|||||||
std::vector<uint8_t> ct, pt;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
|
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
|
ASSERT_TRUE(ct.size() == 16); // 1 + 15 padding
|
||||||
|
|
||||||
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
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);
|
ASSERT_EQ(pt, plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,11 +45,11 @@ TEST(pkcs7_empty_plaintext)
|
|||||||
std::vector<uint8_t> ct, pt;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
|
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)
|
ASSERT_TRUE(ct.size() == 16); // Full block of padding (0x10)
|
||||||
|
|
||||||
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
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);
|
ASSERT_EQ(pt, plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,11 +61,11 @@ TEST(pkcs7_15_byte_plaintext)
|
|||||||
std::vector<uint8_t> ct, pt;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
|
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
|
ASSERT_TRUE(ct.size() == 16); // 15 + 1 padding byte
|
||||||
|
|
||||||
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
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);
|
ASSERT_EQ(pt, plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ TEST(pkcs7_invalid_padding_rejected)
|
|||||||
std::vector<uint8_t> ct, pt;
|
std::vector<uint8_t> ct, pt;
|
||||||
|
|
||||||
auto result = tinyaes::cbc_encrypt_pkcs7(key, iv, plaintext, ct);
|
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)
|
// Flip a bit in the last block (padding block)
|
||||||
ct.back() ^= 0x01;
|
ct.back() ^= 0x01;
|
||||||
@@ -88,3 +88,25 @@ TEST(pkcs7_invalid_padding_rejected)
|
|||||||
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
result = tinyaes::cbc_decrypt_pkcs7(key, iv, ct, pt);
|
||||||
ASSERT_TRUE(result == tinyaes::Result::InvalidPadding);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
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
|
// Test Case 13: AES-256, no plaintext, no AAD
|
||||||
static const uint8_t gcm_tc13_key[] = {
|
static const uint8_t gcm_tc13_key[] = {
|
||||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||||
|
|||||||
Reference in New Issue
Block a user