cryptopals

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

commit 2bc60b7d2f6c303799d76aedb948bb547f491c3e
parent 58d7943c0d6b6b425b0c1bc2b5f3a4d81087dbf8
Author: Jack Sangdahl <jack@pngu.org>
Date:   Tue, 14 Oct 2025 09:12:38 -0600

challenge 14: byte-at-a-time ecb decryption (hard)

Diffstat:
Asrc/14_byte_ecb_decryption_2.c | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 106 insertions(+), 0 deletions(-)

diff --git a/src/14_byte_ecb_decryption_2.c b/src/14_byte_ecb_decryption_2.c @@ -0,0 +1,106 @@ +// Byte-at-a-time ECB decryption (Harder) +#include "aes.h" +#include "base64.h" +#include "blocks.h" +#include "random.h" +#include <assert.h> + +/* + * take your oracle function from [src/12_byte_ecb_decryption_1.c]. now generate + * a random count of random bytes and prepend this string to every plaintext. + * you are now doing: 'aes_128_ecb(random_prefix || attacker_controlled || + * target_bytes, random_key)'. same gaol: decrypt the target bytes. stop and + * think for a second... what's harder than challenge #12 about doing this? how + * would you overcome that obstacle? the hint is: you're using all the tools you + * already have; no crazy math is required. think "STIMULUS" and "RESPONSE". + */ + +const unsigned char *key; +char *secret; +size_t secret_len; +size_t prefix_len; +unsigned char *prefix; + +struct bytes append_encrypt_ecb2(char *data, size_t data_len) { + size_t input_len = prefix_len + data_len + secret_len; + char *input = (char *)malloc(input_len * sizeof(char)); + memcpy(input, prefix, prefix_len); + memcpy(input + prefix_len, data, data_len); + memcpy(input + prefix_len + data_len, secret, secret_len); + struct bytes res = aes_encrypt_ecb(input, input_len, key, 16); + free(input); + return res; +} + +struct bytes probe_encrypt2(size_t len, char *suffix, size_t suffix_len, + size_t padding_len) { + char *input = (char *)malloc((len + padding_len) * sizeof(char)); + memset(input, 'A', len + padding_len - suffix_len); + memcpy(input + len + padding_len - suffix_len, suffix, suffix_len); + struct bytes res = append_encrypt_ecb2(input, len + padding_len); + free(input); + size_t new_len = res.len - 16; + unsigned char *new = (unsigned char *)malloc(new_len * sizeof(unsigned char)); + memcpy(new, res.data + 16, new_len); + free(res.data); + return (struct bytes){new, new_len}; +} + +int main(void) { + srand(time(NULL)); + key = random_bytes(16); + struct bytes encoded, saved = {NULL, 0}; + int block_size, i; + char *base64 = "Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkga" + "GFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdX" + "N0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg"; + prefix_len = random_int_inclusive(2, 12); + prefix = random_bytes(prefix_len); + secret_len = base64_decode(base64, strlen(base64), &secret); + unsigned char **blocks = NULL; + for (i = 32; i < 64; i++) { + encoded = probe_encrypt2(i, 0, 0, 0); + int num_blocks = chunk(encoded.data, encoded.len, 16, &blocks); + for (int j = 0; j < num_blocks - 1; j++) + if (!memcmp(blocks[j], blocks[j + 1], 16)) + goto exit; + } +exit: + free(blocks); + size_t padding_len = i % 16; + printf("Prefix size: %zu\n", 16 - padding_len); + struct bytes zero_encoded = probe_encrypt2(0, 0, 0, padding_len); + printf("Ciphertext size: %zu\n", zero_encoded.len); + for (i = 0; i < 32; i++) { + encoded = probe_encrypt2(i, 0, 0, padding_len); + block_size = encoded.len - zero_encoded.len; + if (block_size > 0) + break; + } + printf("Block size: %d\n", block_size); + size_t res_len = zero_encoded.len - i; + printf("Secret size: %zu\n", res_len); + char ones[64] = {1}; + encoded = append_encrypt_ecb2(ones, 64); + char *detected_mode = aes_oracle(encoded.data, block_size); + printf("%s mode\n\n", detected_mode); + char *res = (char *)malloc(res_len * sizeof(char)); + for (size_t i = 0; i < res_len; i++) { + size_t sz = next_block(i, block_size); + saved = probe_encrypt2(sz - i - 1, 0, 0, padding_len); + for (unsigned char ch = 1; ch <= 128; ch++) { + res[i] = ch; + res[i + 1] = 0; + encoded = probe_encrypt2(sz, res, i + 1, padding_len); + if (!memcmp(encoded.data, saved.data, sz)) + break; + } + } + printf("%s\n", res); + assert(!strcmp(res, secret)); + free(res); + free(zero_encoded.data); + free(encoded.data); + free(saved.data); + return 0; +}