Fix ARM CE byte ordering, expand C/C++ API, and harden build

ARM backends: fix round key byte-swap on little-endian (vrev32q_u8),
rewrite decrypt to pre-process middle keys with InvMixColumns, fix
GHASH PMULL reflect and reduction ordering.

API: add nonce/IV-generating convenience overloads for CTR, CBC, and
GCM (library generates and prepends nonce, appends tag). Add C API
for IV/nonce generation. Rename error codes (TINYAES_OK, Result::Ok,
Result::AuthenticationFailed, etc.).

Build: add MinGW GCC AVX-512 debug alignment fix, harden bench/fuzz
CMake targets (warnings-as-errors, linker hardening), align with
tinysha CMake conventions. Add README.

Tests: expand coverage for nonce-generating API overloads, add NIST
GCM test vectors, improve fuzz target differential testing.
This commit is contained in:
Brandon Lehmann
2026-02-24 21:57:00 -05:00
parent cc49624c7a
commit b4df5d078a
30 changed files with 1646 additions and 277 deletions

View File

@@ -48,6 +48,14 @@ namespace tinyaes
// Note: ARM AES instructions combine SubBytes+ShiftRows (vaese) and MixColumns (vaesmc)
// The key XOR is done separately (veorq).
// Round keys are stored as big-endian uint32_t by the portable key expansion.
// On little-endian ARM64, the bytes within each 32-bit word are reversed in memory.
// vrev32q_u8 restores the original byte order expected by the ARM AES instructions.
static inline uint8x16_t load_rk(const uint8_t *rk8)
{
return vrev32q_u8(vld1q_u8(rk8));
}
void aes_encrypt_block_arm_ce(const uint32_t *rk, int rounds, const uint8_t in[16], uint8_t out[16])
{
const uint8_t *rk8 = reinterpret_cast<const uint8_t *>(rk);
@@ -57,15 +65,15 @@ namespace tinyaes
// So we need: vaese(block, key[i]) then vaesmcq for MixColumns
for (int i = 0; i < rounds - 1; ++i)
{
uint8x16_t key = vld1q_u8(rk8 + i * 16);
uint8x16_t key = load_rk(rk8 + i * 16);
block = vaeseq_u8(block, key);
block = vaesmcq_u8(block);
}
// Last round: no MixColumns
uint8x16_t key_last = vld1q_u8(rk8 + (rounds - 1) * 16);
uint8x16_t key_last = load_rk(rk8 + (rounds - 1) * 16);
block = vaeseq_u8(block, key_last);
// Final AddRoundKey
uint8x16_t key_final = vld1q_u8(rk8 + rounds * 16);
uint8x16_t key_final = load_rk(rk8 + rounds * 16);
block = veorq_u8(block, key_final);
vst1q_u8(out, block);
@@ -76,19 +84,22 @@ namespace tinyaes
const uint8_t *rk8 = reinterpret_cast<const uint8_t *>(rk);
uint8x16_t block = vld1q_u8(in);
// Decryption uses inverse round keys in reverse order
for (int i = rounds; i > 1; --i)
// ARM AESD places AddRoundKey before InvSubBytes, but standard AES
// places it after. Since InvSubBytes is nonlinear, the middle round
// keys must be pre-processed with InvMixColumns to compensate.
// First round key (rk[rounds]) and last round key (rk[0]) are used as-is.
block = vaesdq_u8(block, load_rk(rk8 + rounds * 16));
block = vaesimcq_u8(block);
for (int i = rounds - 1; i > 1; --i)
{
uint8x16_t key = vld1q_u8(rk8 + i * 16);
block = vaesdq_u8(block, key);
block = vaesdq_u8(block, vaesimcq_u8(load_rk(rk8 + i * 16)));
block = vaesimcq_u8(block);
}
// Last round: no InvMixColumns
uint8x16_t key_1 = vld1q_u8(rk8 + 16);
block = vaesdq_u8(block, key_1);
// Last round: key needs InvMixColumns, but no InvMixColumns on state
block = vaesdq_u8(block, vaesimcq_u8(load_rk(rk8 + 16)));
// Final AddRoundKey
uint8x16_t key_0 = vld1q_u8(rk8);
block = veorq_u8(block, key_0);
block = veorq_u8(block, load_rk(rk8));
vst1q_u8(out, block);
}
@@ -104,13 +115,13 @@ namespace tinyaes
for (int r = 0; r < rounds - 1; ++r)
{
uint8x16_t key = vld1q_u8(rk8 + r * 16);
uint8x16_t key = load_rk(rk8 + r * 16);
block = vaeseq_u8(block, key);
block = vaesmcq_u8(block);
}
uint8x16_t key_last = vld1q_u8(rk8 + (rounds - 1) * 16);
uint8x16_t key_last = load_rk(rk8 + (rounds - 1) * 16);
block = vaeseq_u8(block, key_last);
uint8x16_t key_final = vld1q_u8(rk8 + rounds * 16);
uint8x16_t key_final = load_rk(rk8 + rounds * 16);
block = veorq_u8(block, key_final);
// XOR with plaintext

View File

@@ -44,15 +44,16 @@ namespace tinyaes
{
// GF(2^128) multiplication using PMULL (carry-less multiply)
// Inputs/outputs are in standard bit order (vrbitq_u8 applied):
// lane 0 = x^0..x^63 (low), lane 1 = x^64..x^127 (high)
static inline uint8x16_t gf128_mul_pmull(uint8x16_t a, uint8x16_t b)
{
// Reflect byte order for GCM convention
poly64_t a_lo = vgetq_lane_p64(vreinterpretq_p64_u8(a), 0);
poly64_t a_hi = vgetq_lane_p64(vreinterpretq_p64_u8(a), 1);
poly64_t b_lo = vgetq_lane_p64(vreinterpretq_p64_u8(b), 0);
poly64_t b_hi = vgetq_lane_p64(vreinterpretq_p64_u8(b), 1);
// Karatsuba multiplication
// Karatsuba multiplication: A(x)*B(x) where A = a_hi*x^64 + a_lo
poly128_t lo = vmull_p64(a_lo, b_lo);
poly128_t hi = vmull_p64(a_hi, b_hi);
poly128_t mid0 = vmull_p64(a_lo, b_hi);
@@ -62,35 +63,42 @@ namespace tinyaes
uint8x16_t hi_v = vreinterpretq_u8_p128(hi);
uint8x16_t mid = veorq_u8(vreinterpretq_u8_p128(mid0), vreinterpretq_u8_p128(mid1));
// Combine: shift mid and XOR into lo/hi
uint8x16_t mid_lo = vextq_u8(vdupq_n_u8(0), mid, 8);
uint8x16_t mid_hi = vextq_u8(mid, vdupq_n_u8(0), 8);
// Combine mid*x^64 into [hi_v : lo_v]
uint8x16_t mid_lo = vextq_u8(vdupq_n_u8(0), mid, 8); // mid << 64
uint8x16_t mid_hi = vextq_u8(mid, vdupq_n_u8(0), 8); // mid >> 64
lo_v = veorq_u8(lo_v, mid_lo);
hi_v = veorq_u8(hi_v, mid_hi);
// Reduction modulo x^128 + x^7 + x^2 + x + 1
// Using the reflected reduction polynomial 0xC2...01
poly64_t r = (poly64_t)0xC200000000000000ULL;
poly128_t t1 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(hi_v), 0), r);
uint8x16_t f = veorq_u8(lo_v, vreinterpretq_u8_p128(t1));
uint8x16_t f_swap = vextq_u8(f, f, 8);
poly128_t t2 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(f_swap), 0), r);
// 256-bit product = [D3:D2:D1:D0] where hi_v=[D3:D2], lo_v=[D1:D0]
// Reduction modulo p(x) = x^128 + x^7 + x^2 + x + 1
// Since x^128 ≡ x^7+x^2+x+1, let q = 0x87
poly64_t q = (poly64_t)0x87ULL;
return veorq_u8(veorq_u8(f_swap, vreinterpretq_u8_p128(t2)), hi_v);
// Step 1: Reduce D3 — D3*x^192 ≡ (D3*q)*x^64
poly128_t t1 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(hi_v), 1), q);
uint8x16_t t1_v = vreinterpretq_u8_p128(t1);
lo_v = veorq_u8(lo_v, vextq_u8(vdupq_n_u8(0), t1_v, 8)); // D1 ^= T1_lo
hi_v = veorq_u8(hi_v, vextq_u8(t1_v, vdupq_n_u8(0), 8)); // D2 ^= T1_hi
// Step 2: Reduce D2' — D2'*x^128 ≡ D2'*q
poly128_t t2 = vmull_p64(vgetq_lane_p64(vreinterpretq_p64_u8(hi_v), 0), q);
lo_v = veorq_u8(lo_v, vreinterpretq_u8_p128(t2));
return lo_v;
}
void ghash_arm_ce(const uint8_t H[16], const uint8_t *data, size_t data_len, uint8_t Y[16])
{
// GCM uses big-endian bit reflection; ARM PMULL operates on reflected bits
// The byte reversal handles the endianness conversion
uint8x16_t rev_mask = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
uint8x16_t h = vqtbl1q_u8(vld1q_u8(H), rev_mask);
uint8x16_t y = vqtbl1q_u8(vld1q_u8(Y), rev_mask);
// GCM uses MSB-first bit ordering (bit 7 of byte 0 = x^0), but ARM PMULL
// uses LSB-first (bit 0 = x^0). vrbitq_u8 reverses bits within each byte
// to convert between these conventions.
uint8x16_t h = vrbitq_u8(vld1q_u8(H));
uint8x16_t y = vrbitq_u8(vld1q_u8(Y));
size_t full_blocks = data_len / 16;
for (size_t i = 0; i < full_blocks; ++i)
{
uint8x16_t d = vqtbl1q_u8(vld1q_u8(data + i * 16), rev_mask);
uint8x16_t d = vrbitq_u8(vld1q_u8(data + i * 16));
y = veorq_u8(y, d);
y = gf128_mul_pmull(y, h);
}
@@ -100,12 +108,12 @@ namespace tinyaes
{
uint8_t padded[16] = {0};
std::memcpy(padded, data + full_blocks * 16, remainder);
uint8x16_t d = vqtbl1q_u8(vld1q_u8(padded), rev_mask);
uint8x16_t d = vrbitq_u8(vld1q_u8(padded));
y = veorq_u8(y, d);
y = gf128_mul_pmull(y, h);
}
vst1q_u8(Y, vqtbl1q_u8(y, rev_mask));
vst1q_u8(Y, vrbitq_u8(y));
}
} // namespace internal

View File

@@ -42,9 +42,9 @@ namespace tinyaes
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.size() != 16)
return Result::InvalidInput;
return Result::InvalidIVSize;
if (plaintext.empty() || (plaintext.size() % 16) != 0)
return Result::InvalidInput;
return Result::InvalidInputSize;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
@@ -69,7 +69,7 @@ namespace tinyaes
secure_zero(rk, sizeof(rk));
secure_zero(block, sizeof(block));
return Result::Success;
return Result::Ok;
}
Result cbc_decrypt(
@@ -82,9 +82,9 @@ namespace tinyaes
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.size() != 16)
return Result::InvalidInput;
return Result::InvalidIVSize;
if (ciphertext.empty() || (ciphertext.size() % 16) != 0)
return Result::InvalidInput;
return Result::InvalidInputSize;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
@@ -107,7 +107,7 @@ namespace tinyaes
}
secure_zero(rk, sizeof(rk));
return Result::Success;
return Result::Ok;
}
// PKCS#7 padding: append N bytes of value N where N = 16 - (len % 16)
@@ -121,7 +121,7 @@ namespace tinyaes
size_t pad_len = 16 - (plaintext.size() % 16);
std::vector<uint8_t> padded(plaintext.size() + pad_len);
std::memcpy(padded.data(), plaintext.data(), plaintext.size());
std::memset(padded.data() + plaintext.size(), static_cast<int>(pad_len), pad_len);
std::memset(padded.data() + plaintext.size(), static_cast<unsigned char>(pad_len), pad_len);
auto result = cbc_encrypt(key, iv, padded, ciphertext);
secure_zero(padded.data(), padded.size());
@@ -137,34 +137,74 @@ namespace tinyaes
{
std::vector<uint8_t> decrypted;
auto result = cbc_decrypt(key, iv, ciphertext, decrypted);
if (result != Result::Success)
if (result != Result::Ok)
return result;
if (decrypted.empty())
return Result::InvalidPadding;
// Constant-time PKCS#7 validation: scan entire last block
// Fully constant-time PKCS#7 validation: always scan all 16 bytes of last block
uint8_t pad_val = decrypted.back();
if (pad_val == 0 || pad_val > 16)
return Result::InvalidPadding;
// Verify all padding bytes match (constant-time)
volatile uint8_t bad = 0;
size_t start = decrypted.size() - pad_val;
for (size_t i = start; i < decrypted.size(); ++i)
// Range check via arithmetic (no branches):
// bad_range is 0xFF if pad_val is out of [1..16], 0x00 otherwise
uint8_t bad_range = static_cast<uint8_t>(((pad_val - 1) >> 8) | ((16 - pad_val) >> 8));
// Always scan exactly 16 bytes of the last block
const uint8_t *last_block = decrypted.data() + decrypted.size() - 16;
volatile uint8_t bad_pad = 0;
for (unsigned j = 0; j < 16; ++j)
{
bad |= static_cast<uint8_t>(decrypted[i] ^ pad_val);
// should_be_pad is 0xFF when byte j is in the padding region, 0x00 otherwise
// Padding region: positions where (j + pad_val >= 16), i.e. j >= 16 - pad_val
uint8_t should_be_pad = static_cast<uint8_t>((15 - j - pad_val) >> 8);
bad_pad |= static_cast<uint8_t>(should_be_pad & (last_block[j] ^ pad_val));
}
uint8_t bad = static_cast<uint8_t>(bad_range | bad_pad);
if (bad != 0)
{
secure_zero(decrypted.data(), decrypted.size());
return Result::InvalidPadding;
}
size_t start = decrypted.size() - pad_val;
plaintext.assign(decrypted.begin(), decrypted.begin() + static_cast<ptrdiff_t>(start));
secure_zero(decrypted.data(), decrypted.size());
return Result::Success;
return Result::Ok;
}
Result cbc_encrypt_pkcs7(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &iv_and_ciphertext)
{
auto iv = generate_iv();
if (iv.empty())
return Result::InternalError;
std::vector<uint8_t> ct;
auto result = cbc_encrypt_pkcs7(key, iv, plaintext, ct);
if (result != Result::Ok)
return result;
iv_and_ciphertext.resize(16 + ct.size());
std::memcpy(iv_and_ciphertext.data(), iv.data(), 16);
std::memcpy(iv_and_ciphertext.data() + 16, ct.data(), ct.size());
return Result::Ok;
}
Result cbc_decrypt_pkcs7(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv_and_ciphertext,
std::vector<uint8_t> &plaintext)
{
if (iv_and_ciphertext.size() < 32)
return Result::InvalidInputSize;
std::vector<uint8_t> iv(iv_and_ciphertext.begin(), iv_and_ciphertext.begin() + 16);
std::vector<uint8_t> ct(iv_and_ciphertext.begin() + 16, iv_and_ciphertext.end());
return cbc_decrypt_pkcs7(key, iv, ct, plaintext);
}
} // namespace tinyaes
@@ -180,9 +220,9 @@ extern "C" int tinyaes_cbc_encrypt(
size_t ciphertext_len)
{
if (!key || !iv || !plaintext || !ciphertext)
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
if (ciphertext_len < plaintext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + 16);
@@ -191,12 +231,13 @@ extern "C" int tinyaes_cbc_encrypt(
auto result = tinyaes::cbc_encrypt(k, v, pt, ct);
tinyaes::secure_zero(k.data(), k.size());
tinyaes::secure_zero(pt.data(), pt.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
return static_cast<int>(result);
std::memcpy(ciphertext, ct.data(), ct.size());
return TINYAES_SUCCESS;
return TINYAES_OK;
}
extern "C" int tinyaes_cbc_decrypt(
@@ -209,9 +250,9 @@ extern "C" int tinyaes_cbc_decrypt(
size_t plaintext_len)
{
if (!key || !iv || !ciphertext || !plaintext)
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
if (plaintext_len < ciphertext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + 16);
@@ -221,11 +262,15 @@ extern "C" int tinyaes_cbc_decrypt(
auto result = tinyaes::cbc_decrypt(k, v, ct, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
{
tinyaes::secure_zero(pt.data(), pt.size());
return static_cast<int>(result);
}
std::memcpy(plaintext, pt.data(), pt.size());
return TINYAES_SUCCESS;
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_OK;
}
extern "C" int tinyaes_cbc_encrypt_pkcs7(
@@ -238,13 +283,13 @@ extern "C" int tinyaes_cbc_encrypt_pkcs7(
size_t *ciphertext_len)
{
if (!key || !iv || !plaintext || !ciphertext || !ciphertext_len)
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
size_t padded_len = plaintext_len + (16 - (plaintext_len % 16));
if (*ciphertext_len < padded_len)
{
*ciphertext_len = padded_len;
return TINYAES_ERROR_BUFFER_TOO_SMALL;
return TINYAES_INVALID_INPUT_SIZE;
}
std::vector<uint8_t> k(key, key + key_len);
@@ -254,13 +299,14 @@ extern "C" int tinyaes_cbc_encrypt_pkcs7(
auto result = tinyaes::cbc_encrypt_pkcs7(k, v, pt, ct);
tinyaes::secure_zero(k.data(), k.size());
tinyaes::secure_zero(pt.data(), pt.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
return static_cast<int>(result);
std::memcpy(ciphertext, ct.data(), ct.size());
*ciphertext_len = ct.size();
return TINYAES_SUCCESS;
return TINYAES_OK;
}
extern "C" int tinyaes_cbc_decrypt_pkcs7(
@@ -273,7 +319,7 @@ extern "C" int tinyaes_cbc_decrypt_pkcs7(
size_t *plaintext_len)
{
if (!key || !iv || !ciphertext || !plaintext || !plaintext_len)
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + 16);
@@ -283,18 +329,18 @@ extern "C" int tinyaes_cbc_decrypt_pkcs7(
auto result = tinyaes::cbc_decrypt_pkcs7(k, v, ct, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
return static_cast<int>(result);
if (*plaintext_len < pt.size())
{
*plaintext_len = pt.size();
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_ERROR_BUFFER_TOO_SMALL;
return TINYAES_INVALID_INPUT_SIZE;
}
std::memcpy(plaintext, pt.data(), pt.size());
*plaintext_len = pt.size();
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_SUCCESS;
return TINYAES_OK;
}

View File

@@ -43,9 +43,9 @@ namespace tinyaes
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.size() != 16)
return Result::InvalidInput;
return Result::InvalidIVSize;
if (input.empty())
return Result::InvalidInput;
return Result::InvalidInputSize;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
@@ -83,7 +83,73 @@ namespace tinyaes
secure_zero(rk, sizeof(rk));
secure_zero(ctr, sizeof(ctr));
return Result::Success;
return Result::Ok;
}
// Build a 16-byte IV from 12-byte nonce + 4-byte counter starting at 1
static std::vector<uint8_t> nonce_to_iv(const std::vector<uint8_t> &nonce)
{
std::vector<uint8_t> iv(16, 0);
std::memcpy(iv.data(), nonce.data(), 12);
iv[15] = 1; // big-endian counter = 1
return iv;
}
Result ctr_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &ciphertext)
{
if (nonce.size() != 12)
return Result::InvalidNonceSize;
auto iv = nonce_to_iv(nonce);
return ctr_crypt(key, iv, plaintext, ciphertext);
}
Result ctr_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
std::vector<uint8_t> &nonce_and_ciphertext)
{
auto nonce = generate_nonce();
if (nonce.empty())
return Result::InternalError;
std::vector<uint8_t> ct;
auto result = ctr_encrypt(key, nonce, plaintext, ct);
if (result != Result::Ok)
return result;
nonce_and_ciphertext.resize(12 + ct.size());
std::memcpy(nonce_and_ciphertext.data(), nonce.data(), 12);
std::memcpy(nonce_and_ciphertext.data() + 12, ct.data(), ct.size());
return Result::Ok;
}
Result ctr_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &ciphertext,
std::vector<uint8_t> &plaintext)
{
if (nonce.size() != 12)
return Result::InvalidNonceSize;
auto iv = nonce_to_iv(nonce);
return ctr_crypt(key, iv, ciphertext, plaintext);
}
Result ctr_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce_and_ciphertext,
std::vector<uint8_t> &plaintext)
{
if (nonce_and_ciphertext.size() < 13)
return Result::InvalidInputSize;
std::vector<uint8_t> nonce(nonce_and_ciphertext.begin(), nonce_and_ciphertext.begin() + 12);
std::vector<uint8_t> ct(nonce_and_ciphertext.begin() + 12, nonce_and_ciphertext.end());
return ctr_decrypt(key, nonce, ct, plaintext);
}
} // namespace tinyaes
@@ -98,9 +164,9 @@ extern "C" int tinyaes_ctr_crypt(
size_t output_len)
{
if (!key || !iv || !input || !output)
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
if (output_len < input_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + 16);
@@ -109,10 +175,74 @@ extern "C" int tinyaes_ctr_crypt(
auto result = tinyaes::ctr_crypt(k, v, in, out);
tinyaes::secure_zero(k.data(), k.size());
tinyaes::secure_zero(in.data(), in.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
return static_cast<int>(result);
std::memcpy(output, out.data(), out.size());
return TINYAES_SUCCESS;
return TINYAES_OK;
}
extern "C" int tinyaes_ctr_encrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *nonce,
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
size_t ciphertext_len)
{
if (!key || !nonce || !plaintext || !ciphertext)
return TINYAES_INVALID_INPUT_SIZE;
if (ciphertext_len < plaintext_len)
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> n(nonce, nonce + 12);
std::vector<uint8_t> pt(plaintext, plaintext + plaintext_len);
std::vector<uint8_t> ct;
auto result = tinyaes::ctr_encrypt(k, n, pt, ct);
tinyaes::secure_zero(k.data(), k.size());
tinyaes::secure_zero(pt.data(), pt.size());
if (result != tinyaes::Result::Ok)
return static_cast<int>(result);
std::memcpy(ciphertext, ct.data(), ct.size());
return TINYAES_OK;
}
extern "C" int tinyaes_ctr_decrypt(
const uint8_t *key,
size_t key_len,
const uint8_t *nonce,
const uint8_t *ciphertext,
size_t ciphertext_len,
uint8_t *plaintext,
size_t plaintext_len)
{
if (!key || !nonce || !ciphertext || !plaintext)
return TINYAES_INVALID_INPUT_SIZE;
if (plaintext_len < ciphertext_len)
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> n(nonce, nonce + 12);
std::vector<uint8_t> ct(ciphertext, ciphertext + ciphertext_len);
std::vector<uint8_t> pt;
auto result = tinyaes::ctr_decrypt(k, n, ct, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Ok)
{
tinyaes::secure_zero(pt.data(), pt.size());
return static_cast<int>(result);
}
std::memcpy(plaintext, pt.data(), pt.size());
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_OK;
}

View File

@@ -41,7 +41,7 @@ namespace tinyaes
if (rounds == 0)
return Result::InvalidKeySize;
if (plaintext.empty() || (plaintext.size() % 16) != 0)
return Result::InvalidInput;
return Result::InvalidInputSize;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
@@ -56,7 +56,7 @@ namespace tinyaes
}
secure_zero(rk, sizeof(rk));
return Result::Success;
return Result::Ok;
}
Result ecb_decrypt(
@@ -68,7 +68,7 @@ namespace tinyaes
if (rounds == 0)
return Result::InvalidKeySize;
if (ciphertext.empty() || (ciphertext.size() % 16) != 0)
return Result::InvalidInput;
return Result::InvalidInputSize;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
@@ -83,7 +83,7 @@ namespace tinyaes
}
secure_zero(rk, sizeof(rk));
return Result::Success;
return Result::Ok;
}
} // namespace tinyaes
@@ -97,9 +97,9 @@ extern "C" int tinyaes_ecb_encrypt(
size_t ciphertext_len)
{
if (!key || !plaintext || !ciphertext)
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
if (ciphertext_len < plaintext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> pt(plaintext, plaintext + plaintext_len);
@@ -107,12 +107,13 @@ extern "C" int tinyaes_ecb_encrypt(
auto result = tinyaes::ecb_encrypt(k, pt, ct);
tinyaes::secure_zero(k.data(), k.size());
tinyaes::secure_zero(pt.data(), pt.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
return static_cast<int>(result);
std::memcpy(ciphertext, ct.data(), ct.size());
return TINYAES_SUCCESS;
return TINYAES_OK;
}
extern "C" int tinyaes_ecb_decrypt(
@@ -124,9 +125,9 @@ extern "C" int tinyaes_ecb_decrypt(
size_t plaintext_len)
{
if (!key || !ciphertext || !plaintext)
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
if (plaintext_len < ciphertext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> ct(ciphertext, ciphertext + ciphertext_len);
@@ -135,9 +136,13 @@ extern "C" int tinyaes_ecb_decrypt(
auto result = tinyaes::ecb_decrypt(k, ct, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
{
tinyaes::secure_zero(pt.data(), pt.size());
return static_cast<int>(result);
}
std::memcpy(plaintext, pt.data(), pt.size());
return TINYAES_SUCCESS;
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_OK;
}

View File

@@ -123,7 +123,7 @@ namespace tinyaes
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.empty())
return Result::InvalidInput;
return Result::InvalidIVSize;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
@@ -180,7 +180,7 @@ namespace tinyaes
secure_zero(rk, sizeof(rk));
secure_zero(H, sizeof(H));
secure_zero(J0, sizeof(J0));
return Result::Success;
return Result::Ok;
}
Result gcm_decrypt(
@@ -195,9 +195,9 @@ namespace tinyaes
if (rounds == 0)
return Result::InvalidKeySize;
if (iv.empty())
return Result::InvalidInput;
return Result::InvalidIVSize;
if (tag.size() != 16)
return Result::InvalidInput;
return Result::InvalidInputSize;
uint32_t rk[internal::AES_MAX_RK_WORDS];
auto key_expand = internal::get_key_expand();
@@ -226,7 +226,7 @@ namespace tinyaes
secure_zero(H, sizeof(H));
secure_zero(J0, sizeof(J0));
secure_zero(computed_tag, sizeof(computed_tag));
return Result::AuthFailed;
return Result::AuthenticationFailed;
}
// Tag verified — decrypt ciphertext using CTR mode
@@ -264,7 +264,77 @@ namespace tinyaes
secure_zero(H, sizeof(H));
secure_zero(J0, sizeof(J0));
secure_zero(computed_tag, sizeof(computed_tag));
return Result::Success;
return Result::Ok;
}
Result gcm_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &plaintext,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &ciphertext_and_tag)
{
std::vector<uint8_t> ct, tag;
auto result = gcm_encrypt(key, nonce, aad, plaintext, ct, tag);
if (result != Result::Ok)
return result;
ciphertext_and_tag.resize(ct.size() + 16);
if (!ct.empty())
std::memcpy(ciphertext_and_tag.data(), ct.data(), ct.size());
std::memcpy(ciphertext_and_tag.data() + ct.size(), tag.data(), 16);
return Result::Ok;
}
Result gcm_encrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &plaintext,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &nonce_ciphertext_tag)
{
auto nonce = generate_nonce();
if (nonce.empty())
return Result::InternalError;
std::vector<uint8_t> ct_tag;
auto result = gcm_encrypt(key, nonce, plaintext, aad, ct_tag);
if (result != Result::Ok)
return result;
nonce_ciphertext_tag.resize(12 + ct_tag.size());
std::memcpy(nonce_ciphertext_tag.data(), nonce.data(), 12);
std::memcpy(nonce_ciphertext_tag.data() + 12, ct_tag.data(), ct_tag.size());
return Result::Ok;
}
Result gcm_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce,
const std::vector<uint8_t> &ciphertext_and_tag,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &plaintext)
{
if (ciphertext_and_tag.size() < 16)
return Result::InvalidInputSize;
size_t ct_len = ciphertext_and_tag.size() - 16;
std::vector<uint8_t> ct(ciphertext_and_tag.begin(), ciphertext_and_tag.begin() + static_cast<ptrdiff_t>(ct_len));
std::vector<uint8_t> tag(ciphertext_and_tag.end() - 16, ciphertext_and_tag.end());
return gcm_decrypt(key, nonce, aad, ct, tag, plaintext);
}
Result gcm_decrypt(
const std::vector<uint8_t> &key,
const std::vector<uint8_t> &nonce_ciphertext_tag,
const std::vector<uint8_t> &aad,
std::vector<uint8_t> &plaintext)
{
if (nonce_ciphertext_tag.size() < 28) // 12 nonce + 16 tag minimum
return Result::InvalidInputSize;
std::vector<uint8_t> nonce(nonce_ciphertext_tag.begin(), nonce_ciphertext_tag.begin() + 12);
std::vector<uint8_t> ct_tag(nonce_ciphertext_tag.begin() + 12, nonce_ciphertext_tag.end());
return gcm_decrypt(key, nonce, ct_tag, aad, plaintext);
}
} // namespace tinyaes
@@ -283,32 +353,33 @@ extern "C" int tinyaes_gcm_encrypt(
uint8_t tag[16])
{
if (!key || !iv || !tag)
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
if (plaintext_len > 0 && (!plaintext || !ciphertext))
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
if (ciphertext_len < plaintext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + iv_len);
std::vector<uint8_t> a(aad ? aad : key, aad ? aad + aad_len : key);
if (!aad)
a.clear();
std::vector<uint8_t> pt(plaintext ? plaintext : key, plaintext ? plaintext + plaintext_len : key);
if (!plaintext)
pt.clear();
std::vector<uint8_t> a;
if (aad && aad_len > 0)
a.assign(aad, aad + aad_len);
std::vector<uint8_t> pt;
if (plaintext && plaintext_len > 0)
pt.assign(plaintext, plaintext + plaintext_len);
std::vector<uint8_t> ct, t;
auto result = tinyaes::gcm_encrypt(k, v, a, pt, ct, t);
tinyaes::secure_zero(k.data(), k.size());
tinyaes::secure_zero(pt.data(), pt.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
return static_cast<int>(result);
if (!ct.empty())
std::memcpy(ciphertext, ct.data(), ct.size());
std::memcpy(tag, t.data(), 16);
return TINYAES_SUCCESS;
return TINYAES_OK;
}
extern "C" int tinyaes_gcm_decrypt(
@@ -325,30 +396,34 @@ extern "C" int tinyaes_gcm_decrypt(
const uint8_t tag[16])
{
if (!key || !iv || !tag)
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
if (ciphertext_len > 0 && (!ciphertext || !plaintext))
return TINYAES_ERROR_INVALID_INPUT;
return TINYAES_INVALID_INPUT_SIZE;
if (plaintext_len < ciphertext_len)
return TINYAES_ERROR_BUFFER_TOO_SMALL;
return TINYAES_INVALID_INPUT_SIZE;
std::vector<uint8_t> k(key, key + key_len);
std::vector<uint8_t> v(iv, iv + iv_len);
std::vector<uint8_t> a(aad ? aad : key, aad ? aad + aad_len : key);
if (!aad)
a.clear();
std::vector<uint8_t> ct(ciphertext ? ciphertext : key, ciphertext ? ciphertext + ciphertext_len : key);
if (!ciphertext)
ct.clear();
std::vector<uint8_t> a;
if (aad && aad_len > 0)
a.assign(aad, aad + aad_len);
std::vector<uint8_t> ct;
if (ciphertext && ciphertext_len > 0)
ct.assign(ciphertext, ciphertext + ciphertext_len);
std::vector<uint8_t> t(tag, tag + 16);
std::vector<uint8_t> pt;
auto result = tinyaes::gcm_decrypt(k, v, a, ct, t, pt);
tinyaes::secure_zero(k.data(), k.size());
if (result != tinyaes::Result::Success)
if (result != tinyaes::Result::Ok)
{
tinyaes::secure_zero(pt.data(), pt.size());
return static_cast<int>(result);
}
if (!pt.empty())
std::memcpy(plaintext, pt.data(), pt.size());
return TINYAES_SUCCESS;
tinyaes::secure_zero(pt.data(), pt.size());
return TINYAES_OK;
}

View File

@@ -63,4 +63,30 @@ namespace tinyaes
#endif
}
std::vector<uint8_t> generate_iv()
{
std::vector<uint8_t> iv(16);
if (generate_iv(iv.data(), 16) != 0)
iv.clear();
return iv;
}
std::vector<uint8_t> generate_nonce()
{
std::vector<uint8_t> nonce(12);
if (generate_iv(nonce.data(), 12) != 0)
nonce.clear();
return nonce;
}
} // namespace tinyaes
extern "C" int tinyaes_generate_iv(uint8_t out[16])
{
return tinyaes::generate_iv(out, 16);
}
extern "C" int tinyaes_generate_nonce(uint8_t out[12])
{
return tinyaes::generate_iv(out, 12);
}

View File

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