Skip to content

Commit aa2a327

Browse files
Merge pull request #29 from jacksonwalters/add_pkcs7_padding
Add PKCS#7 padding
2 parents 6809a19 + 9c054b0 commit aa2a327

File tree

7 files changed

+281
-76
lines changed

7 files changed

+281
-76
lines changed

include/ecb.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,12 @@ void aes_ecb_encrypt(const uint8_t *plaintext, uint8_t *ciphertext,
1212
void aes_ecb_decrypt(const uint8_t *ciphertext, uint8_t *plaintext,
1313
size_t length, const void *ctx);
1414

15+
int aes_ecb_encrypt_padded(const uint8_t *in, size_t in_len,
16+
const void *ctx,
17+
uint8_t **out, size_t *out_len);
18+
19+
int aes_ecb_decrypt_padded(const uint8_t *in, size_t in_len,
20+
const void *ctx,
21+
uint8_t **out, size_t *out_len);
22+
1523
#endif

include/padding.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#ifndef PADDING_H
2+
#define PADDING_H
3+
4+
#include <stddef.h>
5+
#include <stdint.h>
6+
7+
#define PADDING_OK 0
8+
#define PADDING_ERR_INVALID -1
9+
#define PADDING_ERR_NOMEM -2
10+
11+
/* Pad `in` (length in_len) to multiple of block_size.
12+
* Allocates new buffer in *out (caller frees it).
13+
*/
14+
int pkcs7_pad(const uint8_t *in, size_t in_len, size_t block_size,
15+
uint8_t **out, size_t *out_len);
16+
17+
/* Remove PKCS#7 padding from `in` (length in_len),
18+
* allocates unpadded buffer in *out (caller frees it).
19+
*/
20+
int pkcs7_unpad(const uint8_t *in, size_t in_len, size_t block_size,
21+
uint8_t **out, size_t *out_len);
22+
23+
#endif /* PADDING_H */

src/cbc.c

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,45 @@
11
#include "cbc.h"
2+
#include "padding.h"
23
#include <string.h>
34
#include <stdlib.h>
45

5-
/* PKCS#7 padding helper: returns number of padding bytes appended (1..16) */
6-
static size_t pkcs7_pad(const uint8_t *in, size_t in_len, uint8_t *out) {
7-
size_t pad_len = AES_BLOCK - (in_len % AES_BLOCK);
8-
if (pad_len == 0) pad_len = AES_BLOCK;
9-
/* copy input */
10-
memcpy(out, in, in_len);
11-
/* append pad bytes */
12-
for (size_t i = 0; i < pad_len; ++i) out[in_len + i] = (uint8_t)pad_len;
13-
return pad_len;
14-
}
15-
16-
/* remove PKCS#7 pad; returns 0 on success and sets *out_len to unpadded length.
17-
* Returns non-zero on invalid padding.
18-
*/
19-
static int pkcs7_unpad(uint8_t *buf, size_t buf_len, size_t *out_len) {
20-
if (buf_len == 0 || (buf_len % AES_BLOCK) != 0) return -1;
21-
uint8_t pad = buf[buf_len - 1];
22-
if (pad == 0 || pad > AES_BLOCK) return -2;
23-
/* check that last pad bytes equal pad */
24-
for (size_t i = 0; i < pad; ++i) {
25-
if (buf[buf_len - 1 - i] != pad) return -3;
26-
}
27-
*out_len = buf_len - pad;
28-
return 0;
29-
}
30-
316
/* XOR helper */
32-
static inline void xor_block(uint8_t out[AES_BLOCK], const uint8_t a[AES_BLOCK], const uint8_t b[AES_BLOCK]) {
33-
for (int i = 0; i < AES_BLOCK; ++i) out[i] = a[i] ^ b[i];
7+
static inline void xor_block(uint8_t out[AES_BLOCK],
8+
const uint8_t a[AES_BLOCK],
9+
const uint8_t b[AES_BLOCK])
10+
{
11+
for (int i = 0; i < AES_BLOCK; ++i)
12+
out[i] = a[i] ^ b[i];
3413
}
3514

3615
int aes_cbc_encrypt(const uint8_t *in, size_t in_len,
3716
uint8_t *out, size_t *out_len,
3817
const uint8_t iv[AES_BLOCK],
3918
encrypt_block_fn encrypt, const void *ctx)
4019
{
41-
if (!in || !out || !out_len || !iv || !encrypt) return -1;
42-
43-
/* padded buffer size = ceil(in_len/16)*16 + 16 (if in_len %16 ==0, add a full block) */
44-
size_t padded_len = ((in_len + AES_BLOCK - 1) / AES_BLOCK) * AES_BLOCK;
45-
if (padded_len == in_len) padded_len += AES_BLOCK;
46-
47-
uint8_t *buf = (uint8_t*)malloc(padded_len);
48-
if (!buf) return -2;
20+
if (!out || !out_len || !iv || !encrypt) return -1;
21+
if (!in && in_len > 0) return -1; // NULL only allowed if in_len==0
22+
if (!in) in = (const uint8_t *)""; // empty input is valid
4923

50-
/* create padded plaintext in buf */
51-
size_t pad_len = pkcs7_pad(in, in_len, buf);
52-
(void)pad_len; /* padded_len equals in_len + pad_len */
24+
uint8_t *padded = NULL;
25+
size_t padded_len = 0;
26+
if (pkcs7_pad(in, in_len, AES_BLOCK, &padded, &padded_len) != PADDING_OK)
27+
return -2;
5328

5429
uint8_t prev[AES_BLOCK];
5530
memcpy(prev, iv, AES_BLOCK);
5631

5732
for (size_t off = 0; off < padded_len; off += AES_BLOCK) {
5833
uint8_t block[AES_BLOCK];
59-
xor_block(block, buf + off, prev);
34+
xor_block(block, padded + off, prev);
6035
encrypt(block, out + off, ctx);
61-
/* new prev = ciphertext block */
6236
memcpy(prev, out + off, AES_BLOCK);
6337
}
6438

6539
*out_len = padded_len;
66-
/* wipe sensitive buffer */
67-
memset(buf, 0, padded_len);
68-
free(buf);
6940
memset(prev, 0, AES_BLOCK);
41+
memset(padded, 0, padded_len);
42+
free(padded);
7043
return 0;
7144
}
7245

@@ -83,22 +56,24 @@ int aes_cbc_decrypt(const uint8_t *in, size_t in_len,
8356

8457
for (size_t off = 0; off < in_len; off += AES_BLOCK) {
8558
uint8_t tmp[AES_BLOCK];
86-
decrypt(in + off, tmp, ctx); /* tmp = AES_DEC(Ci) */
87-
xor_block(out + off, tmp, prev); /* plaintext block = tmp XOR prev */
59+
decrypt(in + off, tmp, ctx);
60+
xor_block(out + off, tmp, prev);
8861
memcpy(prev, in + off, AES_BLOCK);
8962
}
9063

91-
/* unpad in-place on out */
92-
size_t unpadded_len = 0;
93-
int r = pkcs7_unpad(out, in_len, &unpadded_len);
94-
if (r != 0) {
95-
/* wipe and return error */
64+
/* Unpad using reusable PKCS#7 function */
65+
uint8_t *unpadded = NULL;
66+
size_t plain_len = 0;
67+
if (pkcs7_unpad(out, in_len, AES_BLOCK, &unpadded, &plain_len) != PADDING_OK) {
9668
memset(out, 0, in_len);
9769
memset(prev, 0, AES_BLOCK);
9870
return -3;
9971
}
10072

101-
*out_len = unpadded_len;
73+
memcpy(out, unpadded, plain_len);
74+
*out_len = plain_len;
75+
memset(unpadded, 0, plain_len);
76+
free(unpadded);
10277
memset(prev, 0, AES_BLOCK);
10378
return 0;
10479
}

src/ecb.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
#include <stdlib.h>
12
#include <string.h>
23
#include "ecb.h"
4+
#include "padding.h"
35

46
void aes_ecb_encrypt(const uint8_t *plaintext, uint8_t *ciphertext,
57
size_t length, const void *ctx)
@@ -18,3 +20,53 @@ void aes_ecb_decrypt(const uint8_t *ciphertext, uint8_t *plaintext,
1820
aes_block_wrapper_dec(ciphertext + i*16, plaintext + i*16, ctx);
1921
}
2022
}
23+
24+
int aes_ecb_encrypt_padded(const uint8_t *in, size_t in_len,
25+
const void *ctx,
26+
uint8_t **out, size_t *out_len)
27+
{
28+
if (!out || !out_len || !ctx) return -1; // mandatory pointers
29+
if (!in && in_len > 0) return -1; // NULL only allowed if in_len>0
30+
if (!in) in = (const uint8_t *)""; // empty input is valid
31+
32+
uint8_t *padded = NULL;
33+
size_t padded_len = 0;
34+
if (pkcs7_pad(in, in_len, 16, &padded, &padded_len) != 0) return -2;
35+
36+
uint8_t *cipher = (uint8_t *)malloc(padded_len);
37+
if (!cipher) { free(padded); return -3; }
38+
39+
for (size_t i = 0; i < padded_len; i += 16)
40+
aes_block_wrapper(padded + i, cipher + i, ctx);
41+
42+
free(padded);
43+
*out = cipher;
44+
*out_len = padded_len;
45+
return 0;
46+
}
47+
48+
int aes_ecb_decrypt_padded(const uint8_t *in, size_t in_len,
49+
const void *ctx,
50+
uint8_t **out, size_t *out_len)
51+
{
52+
if (!in || !ctx || !out || !out_len || (in_len % 16) != 0) return -1;
53+
54+
uint8_t *decrypted = (uint8_t *)malloc(in_len);
55+
if (!decrypted) return -2;
56+
57+
for (size_t i = 0; i < in_len; i += 16)
58+
aes_block_wrapper_dec(in + i, decrypted + i, ctx);
59+
60+
uint8_t *unpadded = NULL;
61+
size_t plain_len = 0;
62+
if (pkcs7_unpad(decrypted, in_len, 16, &unpadded, &plain_len) != 0) {
63+
free(decrypted);
64+
return -3;
65+
}
66+
67+
free(decrypted);
68+
*out = unpadded;
69+
*out_len = plain_len;
70+
return 0;
71+
}
72+

src/padding.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include "padding.h"
2+
#include <stdlib.h>
3+
#include <string.h>
4+
5+
/* Constant-time check: all bytes in buf[0..n-1] == val */
6+
static int ct_mem_is_value(const uint8_t *buf, size_t n, uint8_t val) {
7+
uint8_t diff = 0;
8+
for (size_t i = 0; i < n; ++i) {
9+
diff |= buf[i] ^ val;
10+
}
11+
return diff == 0;
12+
}
13+
14+
int pkcs7_pad(const uint8_t *in, size_t in_len, size_t block_size,
15+
uint8_t **out, size_t *out_len)
16+
{
17+
if (!in || !out || !out_len || block_size == 0 || block_size > 255)
18+
return PADDING_ERR_INVALID;
19+
20+
size_t pad_len = block_size - (in_len % block_size);
21+
if (pad_len == 0) pad_len = block_size; /* full block padding */
22+
23+
size_t total = in_len + pad_len;
24+
uint8_t *buf = (uint8_t *)malloc(total);
25+
if (!buf) return PADDING_ERR_NOMEM;
26+
27+
if (in_len > 0) memcpy(buf, in, in_len);
28+
memset(buf + in_len, (uint8_t)pad_len, pad_len);
29+
30+
*out = buf;
31+
*out_len = total;
32+
return PADDING_OK;
33+
}
34+
35+
int pkcs7_unpad(const uint8_t *in, size_t in_len, size_t block_size,
36+
uint8_t **out, size_t *out_len)
37+
{
38+
if (!in || !out || !out_len || block_size == 0 || block_size > 255)
39+
return PADDING_ERR_INVALID;
40+
if (in_len == 0 || (in_len % block_size) != 0)
41+
return PADDING_ERR_INVALID;
42+
43+
uint8_t pad_val = in[in_len - 1];
44+
if (pad_val == 0 || pad_val > block_size)
45+
return PADDING_ERR_INVALID;
46+
47+
if (!ct_mem_is_value(in + (in_len - pad_val), pad_val, pad_val))
48+
return PADDING_ERR_INVALID;
49+
50+
size_t plain_len = in_len - pad_val;
51+
uint8_t *buf = (uint8_t *)malloc(plain_len ? plain_len : 1);
52+
if (!buf) return PADDING_ERR_NOMEM;
53+
54+
if (plain_len > 0) memcpy(buf, in, plain_len);
55+
56+
*out = buf;
57+
*out_len = plain_len;
58+
return PADDING_OK;
59+
}

tests/test_ecb.c

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
#include <stdio.h>
22
#include <stdint.h>
33
#include <string.h>
4+
#include <stdlib.h>
45

56
#include "../include/ecb.h"
67
#include "../include/aes_wrapper.h"
78
#include "../include/aes_128.h"
89
#include "../include/key_expansion_128.h"
910
#include "../include/sbox.h"
11+
#include "../include/padding.h"
1012

11-
int main(void) {
12-
// NIST SP 800-38A AES-128 ECB test vector
13+
static int test_nist_vector(void) {
1314
const uint8_t key[16] = {
1415
0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,
1516
0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c
@@ -29,38 +30,66 @@ int main(void) {
2930
uint8_t sbox[256];
3031
initialize_aes_sbox(sbox);
3132
aes_key_expansion_128(key, round_keys, sbox);
32-
33-
struct aes_ctx ctx = { .round_keys = round_keys, .sbox = sbox, .key_len = 16};
33+
struct aes_ctx ctx = { .round_keys = round_keys, .sbox = sbox, .key_len = 16 };
3434

3535
uint8_t ciphertext[16] = {0};
3636
uint8_t decrypted[16] = {0};
3737

38-
// Encrypt
3938
aes_ecb_encrypt(plaintext, ciphertext, 16, &ctx);
40-
41-
// Decrypt
4239
aes_ecb_decrypt(ciphertext, decrypted, 16, &ctx);
4340

44-
// Verify encryption matches NIST vector
45-
if (memcmp(ciphertext, expected_ciphertext, 16) != 0) {
46-
fprintf(stderr, "ECB encryption FAILED\n");
47-
return 1;
48-
}
41+
if (memcmp(ciphertext, expected_ciphertext, 16) != 0) return 1;
42+
if (memcmp(decrypted, plaintext, 16) != 0) return 2;
43+
return 0;
44+
}
45+
46+
static int test_partial_block(void) {
47+
const uint8_t key[16] = {0};
48+
uint8_t round_keys[176], sbox[256];
49+
initialize_aes_sbox(sbox);
50+
aes_key_expansion_128(key, round_keys, sbox);
51+
struct aes_ctx ctx = { .round_keys = round_keys, .sbox = sbox, .key_len = 16 };
52+
53+
uint8_t plaintext[5] = {1,2,3,4,5};
54+
uint8_t *cipher = NULL, *plain = NULL;
55+
size_t cipher_len = 0, plain_len = 0;
56+
57+
if (aes_ecb_encrypt_padded(plaintext, 5, &ctx, &cipher, &cipher_len) != 0) return 1;
58+
if (aes_ecb_decrypt_padded(cipher, cipher_len, &ctx, &plain, &plain_len) != 0) return 2;
59+
60+
if (plain_len != 5) return 3;
61+
if (memcmp(plain, plaintext, 5) != 0) return 4;
4962

50-
// Verify decryption matches plaintext
51-
if (memcmp(decrypted, plaintext, 16) != 0) {
52-
fprintf(stderr, "ECB decryption FAILED\n");
53-
return 2;
54-
}
63+
free(cipher);
64+
free(plain);
65+
return 0;
66+
}
67+
68+
static int test_empty(void) {
69+
const uint8_t key[16] = {0};
70+
uint8_t round_keys[176], sbox[256];
71+
initialize_aes_sbox(sbox);
72+
aes_key_expansion_128(key, round_keys, sbox);
73+
struct aes_ctx ctx = { .round_keys = round_keys, .sbox = sbox, .key_len = 16 };
74+
75+
uint8_t *cipher = NULL, *plain = NULL;
76+
size_t cipher_len = 0, plain_len = 0;
5577

56-
printf("ECB round-trip OK — plaintext recovered correctly.\n");
57-
printf("Plaintext: ");
58-
for (size_t i = 0; i < 16; ++i) printf("%02x", plaintext[i]);
59-
printf("\n");
78+
if (aes_ecb_encrypt_padded(NULL, 0, &ctx, &cipher, &cipher_len) != 0) return 1;
79+
if (aes_ecb_decrypt_padded(cipher, cipher_len, &ctx, &plain, &plain_len) != 0) return 2;
6080

61-
printf("Ciphertext (hex): ");
62-
for (size_t i = 0; i < 16; ++i) printf("%02x", ciphertext[i]);
63-
printf("\n");
81+
if (plain_len != 0) return 3;
82+
83+
free(cipher);
84+
free(plain);
85+
return 0;
86+
}
87+
88+
int main(void) {
89+
if (test_nist_vector() != 0) { fprintf(stderr, "NIST ECB test FAILED\n"); return 1; }
90+
if (test_partial_block() != 0) { fprintf(stderr, "Partial-block ECB test FAILED\n"); return 2; }
91+
if (test_empty() != 0) { fprintf(stderr, "Empty ECB test FAILED\n"); return 3; }
6492

93+
printf("All ECB tests passed.\n");
6594
return 0;
6695
}

0 commit comments

Comments
 (0)