Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 78 additions & 15 deletions ctaes.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@

#include "ctaes.h"

#include <stdbool.h>
#include <string.h>

#define BLOCK_SIZE 16 // Standard AES block size

/* Slice variable slice_i contains the i'th bit of the 16 state variables in this order:
* 0 1 2 3
* 4 5 6 7
Expand Down Expand Up @@ -592,41 +595,101 @@ static void AESCBC_decrypt(const AES_state* rounds, uint8_t* iv, int nk, size_t
}
}

void AES128_CBC_init(AES128_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv) {

/**
* Computes the required length of padded plaintext.
* Ensures that the output length is always a multiple of BLOCK_SIZE.
*/
static inline size_t compute_padded_length(size_t plaintext_len) {
size_t pad_size = BLOCK_SIZE - (plaintext_len % BLOCK_SIZE);
return plaintext_len + pad_size;
}

/*
* Applies PKCS#7 Padding https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 to the input plaintext.
*/
static inline void PKCS7_pad(const unsigned char *plain, unsigned char *padded_plain, size_t plaintext_len, size_t padded_len) {
size_t pad_size = padded_len - plaintext_len;

memcpy(padded_plain, plain, plaintext_len); // Copy original data
memset(padded_plain + plaintext_len, pad_size, pad_size); // Apply padding
}

/**
* Removes PKCS#7 padding from decrypted plaintext and validate padding bytes
* Returns true if padding is valid; false otherwise.
*/
static inline bool PKCS7_unpad(unsigned char *plaintext, size_t ciphertext_len, size_t* plaintext_len) {
if (ciphertext_len == 0 || ciphertext_len % BLOCK_SIZE != 0) return false; // Invalid length

size_t pad_size = plaintext[ciphertext_len - 1]; // Last byte indicates padding size
if (pad_size == 0 || pad_size > BLOCK_SIZE || pad_size > ciphertext_len) return false;

// Verify that all padding bytes match pad_size
for (size_t i = ciphertext_len - pad_size; i < ciphertext_len; i++) {
if (plaintext[i] != pad_size) return false;
}

*plaintext_len = ciphertext_len - pad_size; // Exclude padding from plaintext
return true;
}

void AES128_CBC_init(AES128_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv, size_t cipher_len, size_t plaintext_len) {
AES128_init(&(ctx->ctx), key16);
memcpy(ctx->iv, iv, 16);
memcpy(ctx->data.iv, iv, BLOCK_SIZE);
ctx->data.ciphertext_len = cipher_len;
ctx->data.plaintext_len = plaintext_len;
}

void AES192_CBC_init(AES192_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv) {
void AES192_CBC_init(AES192_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv, size_t cipher_len, size_t plaintext_len) {
AES192_init(&(ctx->ctx), key16);
memcpy(ctx->iv, iv, 16);
memcpy(ctx->data.iv, iv, BLOCK_SIZE);
ctx->data.ciphertext_len = cipher_len;
ctx->data.plaintext_len = plaintext_len;
}

void AES256_CBC_init(AES256_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv) {
void AES256_CBC_init(AES256_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv, size_t cipher_len, size_t plaintext_len) {
AES256_init(&(ctx->ctx), key16);
memcpy(ctx->iv, iv, 16);
memcpy(ctx->data.iv, iv, BLOCK_SIZE);
ctx->data.ciphertext_len = cipher_len;
ctx->data.plaintext_len = plaintext_len;
}

void AES128_CBC_encrypt(AES128_CBC_ctx* ctx, size_t blocks, unsigned char* encrypted, const unsigned char* plain) {
AESCBC_encrypt(ctx->ctx.rk, ctx->iv, 10, blocks, encrypted, plain);
ctx->data.padded_len = compute_padded_length(ctx->data.plaintext_len);
unsigned char padded_plain[ctx->data.padded_len]; // Allocate based on computed padding
PKCS7_pad(plain, padded_plain, ctx->data.plaintext_len, ctx->data.padded_len);
ctx->data.ciphertext_len = ctx->data.padded_len;
AESCBC_encrypt(ctx->ctx.rk, ctx->data.iv, 10, blocks, encrypted, padded_plain);
}

void AES128_CBC_decrypt(AES128_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted) {
AESCBC_decrypt(ctx->ctx.rk, ctx->iv, 10, blocks, plain, encrypted);
bool AES128_CBC_decrypt(AES128_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted) {
AESCBC_decrypt(ctx->ctx.rk, ctx->data.iv, 10, blocks, plain, encrypted);
return PKCS7_unpad(plain, ctx->data.ciphertext_len, &ctx->data.plaintext_len);
}

void AES192_CBC_encrypt(AES192_CBC_ctx* ctx, size_t blocks, unsigned char* encrypted, const unsigned char* plain) {
AESCBC_encrypt(ctx->ctx.rk, ctx->iv, 12, blocks, encrypted, plain);
ctx->data.padded_len = compute_padded_length(ctx->data.plaintext_len);
unsigned char padded_plain[ctx->data.padded_len]; // Allocate based on computed padding
PKCS7_pad(plain, padded_plain, ctx->data.plaintext_len, ctx->data.padded_len);
ctx->data.ciphertext_len = ctx->data.padded_len;
AESCBC_encrypt(ctx->ctx.rk, ctx->data.iv, 12, blocks, encrypted, padded_plain);
}

void AES192_CBC_decrypt(AES192_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted) {
AESCBC_decrypt(ctx->ctx.rk, ctx->iv, 12, blocks, plain, encrypted);
bool AES192_CBC_decrypt(AES192_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted) {
AESCBC_decrypt(ctx->ctx.rk, ctx->data.iv, 12, blocks, plain, encrypted);
return PKCS7_unpad(plain, ctx->data.ciphertext_len, &ctx->data.plaintext_len);
}

void AES256_CBC_encrypt(AES256_CBC_ctx* ctx, size_t blocks, unsigned char* encrypted, const unsigned char* plain) {
AESCBC_encrypt(ctx->ctx.rk, ctx->iv, 14, blocks, encrypted, plain);
ctx->data.padded_len = compute_padded_length(ctx->data.plaintext_len);
unsigned char padded_plain[ctx->data.padded_len]; // Allocate based on computed padding
PKCS7_pad(plain, padded_plain, ctx->data.plaintext_len, ctx->data.padded_len);
ctx->data.ciphertext_len = ctx->data.padded_len;
AESCBC_encrypt(ctx->ctx.rk, ctx->data.iv, 14, blocks, encrypted, padded_plain);
}

void AES256_CBC_decrypt(AES256_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted) {
AESCBC_decrypt(ctx->ctx.rk, ctx->iv, 14, blocks, plain, encrypted);
bool AES256_CBC_decrypt(AES256_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted) {
AESCBC_decrypt(ctx->ctx.rk, ctx->data.iv, 14, blocks, plain, encrypted);
return PKCS7_unpad(plain, ctx->data.ciphertext_len, &ctx->data.plaintext_len);
}
26 changes: 17 additions & 9 deletions ctaes.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@
#ifndef CTAES_H
#define CTAES_H

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

typedef struct {
uint16_t slice[8];
} AES_state;

typedef struct {
uint8_t iv[16]; /* iv is updated after each use */
size_t plaintext_len; /* Actual plaintext length */
size_t padded_len; /* Length of plaintext after padding */
size_t ciphertext_len; /* Length of ciphertext */
} AES_CBC_data;

typedef struct {
AES_state rk[11];
} AES128_ctx;
Expand All @@ -28,17 +36,17 @@ typedef struct {

typedef struct {
AES128_ctx ctx;
uint8_t iv[16]; /* iv is updated after each use */
AES_CBC_data data;
} AES128_CBC_ctx;

typedef struct {
AES192_ctx ctx;
uint8_t iv[16]; /* iv is updated after each use */
AES_CBC_data data;
} AES192_CBC_ctx;

typedef struct {
AES256_ctx ctx;
uint8_t iv[16]; /* iv is updated after each use */
AES_CBC_data data;
} AES256_CBC_ctx;

void AES128_init(AES128_ctx* ctx, const unsigned char* key16);
Expand All @@ -53,16 +61,16 @@ void AES256_init(AES256_ctx* ctx, const unsigned char* key32);
void AES256_encrypt(const AES256_ctx* ctx, size_t blocks, unsigned char* cipher16, const unsigned char* plain16);
void AES256_decrypt(const AES256_ctx* ctx, size_t blocks, unsigned char* plain16, const unsigned char* cipher16);

void AES128_CBC_init(AES128_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv);
void AES128_CBC_init(AES128_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv, size_t cipher_len, size_t plaintext_len);
void AES128_CBC_encrypt(AES128_CBC_ctx* ctx, size_t blocks, unsigned char* encrypted, const unsigned char* plain);
void AES128_CBC_decrypt(AES128_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted);
bool AES128_CBC_decrypt(AES128_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted);

void AES192_CBC_init(AES192_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv);
void AES192_CBC_init(AES192_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv, size_t cipher_len, size_t plaintext_len);
void AES192_CBC_encrypt(AES192_CBC_ctx* ctx, size_t blocks, unsigned char* encrypted, const unsigned char* plain);
void AES192_CBC_decrypt(AES192_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted);
bool AES192_CBC_decrypt(AES192_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted);

void AES256_CBC_init(AES256_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv);
void AES256_CBC_init(AES256_CBC_ctx* ctx, const unsigned char* key16, const uint8_t* iv, size_t cipher_len, size_t plaintext_len);
void AES256_CBC_encrypt(AES256_CBC_ctx* ctx, size_t blocks, unsigned char* encrypted, const unsigned char* plain);
void AES256_CBC_decrypt(AES256_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted);
bool AES256_CBC_decrypt(AES256_CBC_ctx* ctx, size_t blocks, unsigned char* plain, const unsigned char *encrypted);

#endif /* CTAES_H */
123 changes: 93 additions & 30 deletions test.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "ctaes.h"

#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
Expand All @@ -21,7 +22,6 @@ typedef struct {
int keysize;
const char* key;
const char* iv;
int nblocks;
const char* plain;
const char* cipher;
} ctaes_cbc_test;
Expand Down Expand Up @@ -50,19 +50,46 @@ static const ctaes_test ctaes_tests[] = {
static const ctaes_cbc_test ctaes_cbc_tests[] = {
/* AES-CBC test vectors from NIST sp800-38a. */
{
128, "2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090a0b0c0d0e0f", 4,
// Full block length will pad 1 more block
128, "2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090a0b0c0d0e0f",
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
"7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7"
"a12b0386d815a2c18e9c23b63a7adbf2fd5576ec89b64bef8fff20c67a2db525d6ddfa8efd7b0fcd869ffe84564e603e6df965ae0dc86e097bdf29bfaa45cd57718fb6e84747912749339489f2c17c4d"
},
{
192, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "000102030405060708090a0b0c0d0e0f", 4,
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
"4f021db243bc633d7178183a9fa071e8b4d9ada9ad7dedf4e5e738763f69145a571b242012fb7ae07fa9baac3df102e008b0e27988598881d920a9e64f5615cd"
// Incomplete block length pad will pad the remaining bytes to fill block
128, "2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090a0b0c0d0e0f",
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b41",
"a12b0386d815a2c18e9c23b63a7adbf2fd5576ec89b64bef8fff20c67a2db525d6ddfa8efd7b0fcd869ffe84564e603e3501ddd9be60093899819bbc115cfe86"
},
{
256, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "000102030405060708090a0b0c0d0e0f", 4,
// Full block length will pad 1 more block
192, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "000102030405060708090a0b0c0d0e0f",
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
"f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b"
"17701a9d29c91a94ceed723c34e87abe1c96845ca8b7e8586dfef2fa6bed24098a52cee8d76db67bfde21553d31c2833f77eb59500ac4903bc7076b18465d0ea7e38ba13918e47c316e53ee336370345"
},
{
// Incomplete block length pad will pad the remaining bytes to fill block
192, "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "000102030405060708090a0b0c0d0e0f",
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417b",
"17701a9d29c91a94ceed723c34e87abe1c96845ca8b7e8586dfef2fa6bed24098a52cee8d76db67bfde21553d31c28339ad0a856f760c64a6bbb3df2dbecdb53"
},
{
// Full block length will pad 1 more block
256, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "000102030405060708090a0b0c0d0e0f",
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445",
"f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461164e1f6488d14e7407e98486d3da86f0"
},
{
// Incomplete block length pad will pad the remaining bytes to fill block
256, "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "000102030405060708090a0b0c0d0e0f",
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac",
"f58c4c04d6e5f1ba779eabfb5f7bfbd68d99fefe25debc6c0b1eedaac5e98e7e"
},
/* AES-CBC padded test vector randomly generated. */
{
128, "d2e4c2c3b3c1fe4878a7bf99807ffe2f", "14e7903c078a28514885abac58618a30",
"5468697320697320612036332d62797465206c6f6e6720706c61696e74657874206578616d706c6520666f72204145532d434243206d6f6465212121",
"35866e380595f69503f3700004d2e57a732522827550158b0e64ee9307d8d58477699f0706f33690aed4147987f9ab8485611ba9662bf2e41aefa170810f625d"
}
};

Expand Down Expand Up @@ -130,47 +157,83 @@ int main(void) {
}
}
for (i = 0; i < sizeof(ctaes_cbc_tests) / sizeof(ctaes_cbc_tests[0]); i++) {
// Define AES block size (16 bytes for AES).
const int block_size = 16;

// Retrieve the test case.
const ctaes_cbc_test* test = &ctaes_cbc_tests[i];
unsigned char key[32], iv[16], plain[4 * 16], cipher[4 * 16], ciphered[4 * 16], deciphered[4 * 16];

// Compute plaintext length (hex string length divided by 2).
const int plain_len = strlen(test->plain) / 2;

// Compute the padded plaintext length for PKCS#7.
// If plaintext is already a multiple of block_size, an extra block is needed.
const int padded_plain_len = (plain_len % block_size == 0)
? (plain_len + block_size)
: (plain_len + (block_size - (plain_len % block_size)));

// Compute the number of AES blocks.
const int blocks = padded_plain_len / block_size;

// Compute ciphertext length (hex string length divided by 2).
const int cipher_len = strlen(test->cipher) / 2;

// Allocate buffers for encryption/decryption.
unsigned char key[32]; // Key buffer (up to 256-bit keys, i.e., 32 bytes).
unsigned char iv[block_size]; // Initialization vector (IV).
unsigned char plain[plain_len]; // Buffer to store original plaintext.
unsigned char cipher[cipher_len]; // Expected ciphertext.
unsigned char ciphered[padded_plain_len]; // Encrypted ciphertext (including padding).
unsigned char deciphered[padded_plain_len]; // Decrypted plaintext (including padding).

// Ensure key size is valid (AES supports only 128, 192, or 256 bits).
assert(test->keysize == 128 || test->keysize == 192 || test->keysize == 256);
assert(test->nblocks == 4);
from_hex(iv, 16, test->iv);
from_hex(plain, test->nblocks * 16, test->plain);
from_hex(cipher, test->nblocks * 16, test->cipher);

// Convert hex-encoded test data into byte arrays.
from_hex(iv, block_size, test->iv); // Convert IV from hex string to byte array.
from_hex(plain, plain_len, test->plain); // Convert plaintext from hex string to byte array.
from_hex(cipher, cipher_len, test->cipher); // Convert expected ciphertext from hex string to byte array.

switch (test->keysize) {
case 128: {
AES128_CBC_ctx ctx;
from_hex(key, 16, test->key);
AES128_CBC_init(&ctx, key, iv);
AES128_CBC_encrypt(&ctx, test->nblocks, ciphered, plain);
AES128_CBC_init(&ctx, key, iv);
AES128_CBC_decrypt(&ctx, test->nblocks, deciphered, cipher);
AES128_CBC_init(&ctx, key, iv, /*cipher_len*/0, plain_len);
AES128_CBC_encrypt(&ctx, blocks, ciphered, plain);

// Initialize decryption with the ciphertext length.
AES128_CBC_init(&ctx, key, iv, ctx.data.ciphertext_len, /*plain_len*/0);
assert(AES128_CBC_decrypt(&ctx, blocks, deciphered, ciphered));
assert(ctx.data.plaintext_len == plain_len);
break;
}
case 192: {
AES192_CBC_ctx ctx;
from_hex(key, 24, test->key);
AES192_CBC_init(&ctx, key, iv);
AES192_CBC_encrypt(&ctx, test->nblocks, ciphered, plain);
AES192_CBC_init(&ctx, key, iv);
AES192_CBC_decrypt(&ctx, test->nblocks, deciphered, cipher);
AES192_CBC_init(&ctx, key, iv, /*cipher_len*/0, plain_len);
AES192_CBC_encrypt(&ctx, blocks, ciphered, plain);

// Initialize decryption with the ciphertext length.
AES192_CBC_init(&ctx, key, iv, ctx.data.ciphertext_len, /*plain_len*/0);
assert(AES192_CBC_decrypt(&ctx, blocks, deciphered, ciphered));
assert(ctx.data.plaintext_len == plain_len);
break;
}
case 256: {
AES256_CBC_ctx ctx;
from_hex(key, 32, test->key);
AES256_CBC_init(&ctx, key, iv);
AES256_CBC_encrypt(&ctx, test->nblocks, ciphered, plain);
AES256_CBC_init(&ctx, key, iv);
AES256_CBC_decrypt(&ctx, test->nblocks, deciphered, cipher);
AES256_CBC_init(&ctx, key, iv, /*cipher_len*/0, plain_len);
AES256_CBC_encrypt(&ctx, blocks, ciphered, plain);

// Initialize decryption with the ciphertext length.
AES256_CBC_init(&ctx, key, iv, ctx.data.ciphertext_len, /*plain_len*/0);
assert(AES256_CBC_decrypt(&ctx, blocks, deciphered, ciphered));
assert(ctx.data.plaintext_len == plain_len);
break;
}
}
if (memcmp(cipher, ciphered, test->nblocks * 16)) {
if (memcmp(cipher, ciphered, cipher_len)) {
fprintf(stderr, "E(key=\"%s\", plain=\"%s\") != \"%s\"\n", test->key, test->plain, test->cipher);
fail++;
}
if (memcmp(plain, deciphered, test->nblocks * 16)) {
if (memcmp(plain, deciphered, plain_len)) {
fprintf(stderr, "D(key=\"%s\", cipher=\"%s\") != \"%s\"\n", test->key, test->cipher, test->plain);
fail++;
}
Expand Down