cryptopals

Cryptopals Challenges
git clone https://git.pngu.org/cryptopals
Log | Files | Refs

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:
Adata/10.txt | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/aes.c | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/aes.h | 9+++++++++
Alib/misc.c | 11+++++++++++
Alib/misc.h | 6++++++
Asrc/10_implement_cbc_mode.c | 37+++++++++++++++++++++++++++++++++++++
6 files changed, 223 insertions(+), 0 deletions(-)

diff --git a/data/10.txt b/data/10.txt @@ -0,0 +1,64 @@ +CRIwqt4+szDbqkNY+I0qbNXPg1XLaCM5etQ5Bt9DRFV/xIN2k8Go7jtArLIy +P605b071DL8C+FPYSHOXPkMMMFPAKm+Nsu0nCBMQVt9mlluHbVE/yl6VaBCj +NuOGvHZ9WYvt51uR/lklZZ0ObqD5UaC1rupZwCEK4pIWf6JQ4pTyPjyiPtKX +g54FNQvbVIHeotUG2kHEvHGS/w2Tt4E42xEwVfi29J3yp0O/TcL7aoRZIcJj +MV4qxY/uvZLGsjo1/IyhtQp3vY0nSzJjGgaLYXpvRn8TaAcEtH3cqZenBoox +BH3MxNjD/TVf3NastEWGnqeGp+0D9bQx/3L0+xTf+k2VjBDrV9HPXNELRgPN +0MlNo79p2gEwWjfTbx2KbF6htgsbGgCMZ6/iCshy3R8/abxkl8eK/VfCGfA6 +bQQkqs91bgsT0RgxXSWzjjvh4eXTSl8xYoMDCGa2opN/b6Q2MdfvW7rEvp5m +wJOfQFDtkv4M5cFEO3sjmU9MReRnCpvalG3ark0XC589rm+42jC4/oFWUdwv +kzGkSeoabAJdEJCifhvtGosYgvQDARUoNTQAO1+CbnwdKnA/WbQ59S9MU61Q +KcYSuk+jK5nAMDot2dPmvxZIeqbB6ax1IH0cdVx7qB/Z2FlJ/U927xGmC/RU +FwoXQDRqL05L22wEiF85HKx2XRVB0F7keglwX/kl4gga5rk3YrZ7VbInPpxU +zgEaE4+BDoEqbv/rYMuaeOuBIkVchmzXwlpPORwbN0/RUL89xwOJKCQQZM8B +1YsYOqeL3HGxKfpFo7kmArXSRKRHToXuBgDq07KS/jxaS1a1Paz/tvYHjLxw +Y0Ot3kS+cnBeq/FGSNL/fFV3J2a8eVvydsKat3XZS3WKcNNjY2ZEY1rHgcGL +5bhVHs67bxb/IGQleyY+EwLuv5eUwS3wljJkGcWeFhlqxNXQ6NDTzRNlBS0W +4CkNiDBMegCcOlPKC2ZLGw2ejgr2utoNfmRtehr+3LAhLMVjLyPSRQ/zDhHj +Xu+Kmt4elmTmqLgAUskiOiLYpr0zI7Pb4xsEkcxRFX9rKy5WV7NhJ1lR7BKy +alO94jWIL4kJmh4GoUEhO+vDCNtW49PEgQkundV8vmzxKarUHZ0xr4feL1ZJ +THinyUs/KUAJAZSAQ1Zx/S4dNj1HuchZzDDm/nE/Y3DeDhhNUwpggmesLDxF +tqJJ/BRn8cgwM6/SMFDWUnhkX/t8qJrHphcxBjAmIdIWxDi2d78LA6xhEPUw +NdPPhUrJcu5hvhDVXcceZLa+rJEmn4aftHm6/Q06WH7dq4RaaJePP6WHvQDp +zZJOIMSEisApfh3QvHqdbiybZdyErz+yXjPXlKWG90kOz6fx+GbvGcHqibb/ +HUfcDosYA7lY4xY17llY5sibvWM91ohFN5jyDlHtngi7nWQgFcDNfSh77TDT +zltUp9NnSJSgNOOwoSSNWadm6+AgbXfQNX6oJFaU4LQiAsRNa7vX/9jRfi65 +5uvujM4ob199CZVxEls10UI9pIemAQQ8z/3rgQ3eyL+fViyztUPg/2IvxOHv +eexE4owH4Fo/bRlhZK0mYIamVxsRADBuBlGqx1b0OuF4AoZZgUM4d8v3iyUu +feh0QQqOkvJK/svkYHn3mf4JlUb2MTgtRQNYdZKDRgF3Q0IJaZuMyPWFsSNT +YauWjMVqnj0AEDHh6QUMF8bXLM0jGwANP+r4yPdKJNsoZMpuVoUBJYWnDTV+ +8Ive6ZgBi4EEbPbMLXuqDMpDi4XcLE0UUPJ8VnmO5fAHMQkA64esY2QqldZ+ +5gEhjigueZjEf0917/X53ZYWJIRiICnmYPoM0GSYJRE0k3ycdlzZzljIGk+P +Q7WgeJhthisEBDbgTuppqKNXLbNZZG/VaTdbpW1ylBv0eqamFOmyrTyh1APS +Gn37comTI3fmN6/wmVnmV4/FblvVwLuDvGgSCGPOF8i6FVfKvdESs+yr+1AE +DJXfp6h0eNEUsM3gXaJCknGhnt3awtg1fSUiwpYfDKZxwpPOYUuer8Wi+VCD +sWsUpkMxhhRqOBKaQaBDQG+kVJu6aPFlnSPQQTi1hxLwi0l0Rr38xkr+lHU7 +ix8LeJVgNsQdtxbovE3i7z3ZcTFY7uJkI9j9E0muDN9x8y/YN25rm6zULYaO +jUoP/7FQZsSgxPIUvUiXkEq+FU2h0FqAC7H18cr3Za5x5dpw5nwawMArKoqG +9qlhqc34lXV0ZYwULu58EImFIS8+kITFuu7jOeSXbBgbhx8zGPqavRXeiu0t +bJd0gWs+YgMLzXtQIbQuVZENMxJSZB4aw5lPA4vr1fFBsiU4unjOEo/XAgwr +Tc0w0UndJFPvXRr3Ir5rFoIEOdRo+6os5DSlk82SBnUjwbje7BWsxWMkVhYO +6bOGUm4VxcKWXu2jU66TxQVIHy7WHktMjioVlWJdZC5Hq0g1LHg1nWSmjPY2 +c/odZqN+dBBC51dCt4oi5UKmKtU5gjZsRSTcTlfhGUd6DY4Tp3CZhHjQRH4l +Zhg0bF/ooPTxIjLKK4r0+yR0lyRjqIYEY27HJMhZDXFDxBQQ1UkUIhAvXacD +WB2pb3YyeSQjt8j/WSbQY6TzdLq8SreZiuMWcXmQk4EH3xu8bPsHlcvRI+B3 +gxKeLnwrVJqVLkf3m2cSGnWQhSLGbnAtgQPA6z7u3gGbBmRtP0KnAHWSK7q6 +onMoYTH+b5iFjCiVRqzUBVzRRKjAL4rcL2nYeV6Ec3PlnboRzJwZIjD6i7WC +dcxERr4WVOjOBX4fhhKUiVvlmlcu8CkIiSnZENHZCpI41ypoVqVarHpqh2aP +/PS624yfxx2N3C2ci7VIuH3DcSYcaTXEKhz/PRLJXkRgVlWxn7QuaJJzDvpB +oFndoRu1+XCsup/AtkLidsSXMFTo/2Ka739+BgYDuRt1mE9EyuYyCMoxO/27 +sn1QWMMd1jtcv8Ze42MaM4y/PhAMp2RfCoVZALUS2K7XrOLl3s9LDFOdSrfD +8GeMciBbfLGoXDvv5Oqq0S/OvjdID94UMcadpnSNsist/kcJJV0wtRGfALG2 ++UKYzEj/2TOiN75UlRvA5XgwfqajOvmIIXybbdhxpjnSB04X3iY82TNSYTmL +LAzZlX2vmV9IKRRimZ2SpzNpvLKeB8lDhIyGzGXdiynQjFMNcVjZlmWHsH7e +ItAKWmCwNkeuAfFwir4TTGrgG1pMje7XA7kMT821cYbLSiPAwtlC0wm77F0T +a7jdMrLjMO29+1958CEzWPdzdfqKzlfBzsba0+dS6mcW/YTHaB4bDyXechZB +k/35fUg+4geMj6PBTqLNNWXBX93dFC7fNyda+Lt9cVJnlhIi/61fr0KzxOeX +NKgePKOC3Rz+fWw7Bm58FlYTgRgN63yFWSKl4sMfzihaQq0R8NMQIOjzuMl3 +Ie5ozSa+y9g4z52RRc69l4n4qzf0aErV/BEe7FrzRyWh4PkDj5wy5ECaRbfO +7rbs1EHlshFvXfGlLdEfP2kKpT9U32NKZ4h+Gr9ymqZ6isb1KfNov1rw0KSq +YNP+EyWCyLRJ3EcOYdvVwVb+vIiyzxnRdugB3vNzaNljHG5ypEJQaTLphIQn +lP02xcBpMNJN69bijVtnASN/TLV5ocYvtnWPTBKu3OyOkcflMaHCEUgHPW0f +mGfld4i9Tu35zrKvTDzfxkJX7+KJ72d/V+ksNKWvwn/wvMOZsa2EEOfdCidm +oql027IS5XvSHynQtvFmw0HTk9UXt8HdVNTqcdy/jUFmXpXNP2Wvn8PrU2Dh +kkIzWhQ5Rxd/vnM2QQr9Cxa2J9GXEV3kGDiZV90+PCDSVGY4VgF8y7GedI1h 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; +}