commit 419e41293a37b19d53da3dd4fd7a37bdce5566cb
parent 8bdc32650426b580fc9972834fa2e3dd067fbe1e
Author: Jack Sangdahl <jack@pngu.org>
Date: Wed, 8 Oct 2025 09:08:51 -0600
challenge 10: implement cbc mode
Diffstat:
6 files changed, 223 insertions(+), 0 deletions(-)
diff --git a/data/10.txt b/data/10.txt
@@ -0,0 +1,64 @@
+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
diff --git a/lib/aes.c b/lib/aes.c
@@ -131,3 +131,99 @@ struct bytes aes_decrypt_ecb(const unsigned char *data, size_t data_len,
free(plaintext);
return unpadded;
}
+
+struct bytes aes_encrypt_cbc(char *data, const size_t data_len,
+ const unsigned char *key, const size_t key_len,
+ unsigned char *iv) {
+ if (key_len != 16 && key_len != 24 && key_len != 32)
+ return (struct bytes){NULL, 0};
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ const EVP_CIPHER *cipher;
+ switch (key_len) {
+ case 16:
+ EVP_aes_128_cbc();
+ break;
+ case 24:
+ EVP_aes_192_cbc();
+ break;
+ case 32:
+ EVP_aes_256_cbc();
+ break;
+ }
+ if (EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ return (struct bytes){NULL, 0};
+ }
+ struct bytes padded = pkcs7(data, data_len, AES_BLOCK_SIZE);
+ if (!padded.data) {
+ EVP_CIPHER_CTX_free(ctx);
+ return (struct bytes){NULL, 0};
+ }
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+ unsigned char *ciphertext =
+ (unsigned char *)malloc(padded.len * sizeof(unsigned char));
+ int len;
+ if (EVP_EncryptUpdate(ctx, ciphertext, &len, padded.data, padded.len) != 1) {
+ free(padded.data);
+ free(ciphertext);
+ EVP_CIPHER_CTX_free(ctx);
+ return (struct bytes){NULL, 0};
+ }
+ int ciphertext_len = len;
+ if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) {
+ free(padded.data);
+ free(ciphertext);
+ EVP_CIPHER_CTX_free(ctx);
+ return (struct bytes){NULL, 0};
+ }
+ ciphertext_len += len;
+ free(padded.data);
+ EVP_CIPHER_CTX_free(ctx);
+ return (struct bytes){ciphertext, ciphertext_len};
+}
+
+struct bytes aes_decrypt_cbc(unsigned char *data, size_t data_len,
+ const unsigned char *key, size_t key_len,
+ unsigned char *iv) {
+ if (key_len != 16 && key_len != 24 && key_len != 32)
+ return (struct bytes){NULL, 0};
+ if (!data_len || data_len % AES_BLOCK_SIZE)
+ return (struct bytes){NULL, 0};
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ const EVP_CIPHER *cipher;
+ switch (key_len) {
+ case 16:
+ cipher = EVP_aes_128_cbc();
+ break;
+ case 24:
+ cipher = EVP_aes_192_cbc();
+ break;
+ case 32:
+ cipher = EVP_aes_256_cbc();
+ break;
+ }
+ if (EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ return (struct bytes){NULL, 0};
+ }
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+ unsigned char *plaintext =
+ (unsigned char *)malloc(data_len * sizeof(unsigned char));
+ int len;
+ if (EVP_DecryptUpdate(ctx, plaintext, &len, data, data_len) != 1) {
+ free(plaintext);
+ EVP_CIPHER_CTX_free(ctx);
+ return (struct bytes){NULL, 0};
+ }
+ int plaintext_len = len;
+ if (EVP_DecryptFinal_ex(ctx, plaintext + len, &len) != 1) {
+ free(plaintext);
+ EVP_CIPHER_CTX_free(ctx);
+ return (struct bytes){NULL, 0};
+ }
+ plaintext_len += len;
+ EVP_CIPHER_CTX_free(ctx);
+ struct bytes res = pkcs7_validate_strip(plaintext, plaintext_len);
+ free(plaintext);
+ return res;
+}
diff --git a/lib/aes.h b/lib/aes.h
@@ -1,5 +1,6 @@
#pragma once
+#include "xor.h"
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <stdbool.h>
@@ -12,11 +13,19 @@ struct bytes {
};
int next_block(int length, size_t block_size);
+
struct bytes pkcs7(const char *data, const size_t data_len, size_t block_size);
struct bytes pkcs7_strip(unsigned char *data, size_t data_len);
bool pkcs7_validate(unsigned char *data, size_t data_len);
struct bytes pkcs7_validate_strip(unsigned char *data, size_t data_len);
+
struct bytes aes_encrypt_ecb(char *data, const size_t data_len,
const unsigned char *key, const size_t key_len);
struct bytes aes_decrypt_ecb(const unsigned char *data, size_t data_len,
const unsigned char *key, size_t key_len);
+struct bytes aes_encrypt_cbc(char *data, const size_t data_len,
+ const unsigned char *key, const size_t key_len,
+ unsigned char *iv);
+struct bytes aes_decrypt_cbc(unsigned char *data, size_t data_len,
+ const unsigned char *key, size_t key_len,
+ unsigned char *iv);
diff --git a/lib/misc.c b/lib/misc.c
@@ -0,0 +1,11 @@
+#include "misc.h"
+
+bool contains(const unsigned char *data, size_t data_len, const char *expr) {
+ size_t expr_len = strlen(expr);
+ if (expr_len > data_len)
+ return false;
+ for (size_t i = 0; i < data_len - expr_len; i++)
+ if (!memcmp(data + i, expr, expr_len))
+ return true;
+ return false;
+}
diff --git a/lib/misc.h b/lib/misc.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <stdbool.h>
+#include <string.h>
+
+bool contains(const unsigned char *data, size_t data_len, const char *expr);
diff --git a/src/10_implement_cbc_mode.c b/src/10_implement_cbc_mode.c
@@ -0,0 +1,37 @@
+// Implement CBC mode
+#include "aes.h"
+#include "base64.h"
+#include "file.h"
+
+/*
+ * cbc mode is a block cipher mode that allows us to encrypt irregularly sized
+ * messages, despite the fact that a block cipher natively only transforms
+ * individual blocks. in cbc mode, each ciphertext block is added to the next
+ * plaintext block before the next call to the cipher core. the first plaintext
+ * block, which has no associated previous ciphertext block, is added to a "fake
+ * 0th ciphertext block" called the initialisation vector, or iv. implement cbc
+ * mode by hand by taking the ecb function you wrote earlier, making it encrypt
+ * instead of decrypt (verify this by deccrypting whatever you encrypt to test),
+ * and using your xor function from the previous exercise to combine them.
+ * [data/10.txt] is intelligible (somewhat) when cbc decrypted against "YELLOW
+ * SUBMARINE" with an iv of all ascii 0 (\x00\x00\x00 &c). do not use openssl's
+ * cbc code to do cbc mode, even to verify your results.
+ */
+
+int main(void) {
+ static char *key = "YELLOW SUBMARINE";
+ char *input = NULL, *decoded = NULL;
+ int file_len = read_file("data/10.txt", &input);
+ int decoded_len = base64_decode(input, file_len, &decoded);
+ unsigned char iv[16] = {0};
+ struct bytes res =
+ aes_decrypt_cbc((unsigned char *)decoded, decoded_len,
+ (const unsigned char *)key, strlen(key), iv);
+ if (res.data) {
+ printf("%s\n", res.data);
+ free(res.data);
+ }
+ if (decoded)
+ free(decoded);
+ return 0;
+}