cryptopals

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

commit b7c3a969c06037f3f56be3ff125723e89d8a5ece
parent 6faa2cffc1514f62f81440c66fe177b47304e17e
Author: Jack Sangdahl <jack@pngu.org>
Date:   Sat, 25 Oct 2025 21:08:56 -0600

challenge 24: create the mt19937 stream cipher and break it

Diffstat:
Asrc/24_create_mt19937_stream_cipher_break.c | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+), 0 deletions(-)

diff --git a/src/24_create_mt19937_stream_cipher_break.c b/src/24_create_mt19937_stream_cipher_break.c @@ -0,0 +1,70 @@ +// Create the MT19937 stream cipher and break it +#include "aes.h" +#include "mt19937.h" +#include "random.h" +#include <assert.h> + +/* + * you can create a trivial stream cipher out of any prng; use it to generate a + * sequence of 8 bit output and call those outputs a keystream. xor each byte of + * plaintext with each successive byte of keystream. write the function that + * does this for mt19937 using a 16-bit seed. verify that you can encrypt and + * decrypt properly. this code should look similar to your ctr code. use your + * function to encrypt a known plaintext (say, 14 consecutive 'a' characters) + * prefixed by a random number of random characters. from the ciphertext, + * recover the "key" (the 16 bit seed). use the same idea to generate a random + * "password reset token" using mt19937 seeded from the current time. write a + * function to check if any given password token is actually the product of an + * mt19937 prng seeded with the current time. + */ + +uint16_t rand_key; + +struct bytes mt19937_encrypt_decrypt(uint32_t key, const unsigned char *data, + size_t data_len) { + unsigned char *encrypted = + (unsigned char *)calloc(data_len + 1, sizeof(unsigned char)); + struct mt19937_state mt = mt19937_init(key); + for (size_t i = 0; i < data_len; i++) + encrypted[i] = data[i] ^ mt19937_extract(&mt); + return (struct bytes){encrypted, data_len}; +} + +struct bytes prefix_encrypt(unsigned char *data, size_t data_len) { + size_t prefix_len = random_int_inclusive(2, 12); + unsigned char *prefix = random_bytes(prefix_len); + size_t input_len = prefix_len + data_len; + unsigned char *input = + (unsigned char *)malloc(input_len * sizeof(unsigned char)); + memcpy(input, prefix, prefix_len); + memcpy(input + prefix_len, data, data_len); + rand_key = rand() % 0xFFFF; + struct bytes res = mt19937_encrypt_decrypt(rand_key, input, input_len); + free(prefix); + free(input); + return res; +} + +int main(void) { + srand(time(NULL)); + uint32_t key = 0xC0FFEE; + static char *data = "AAAAAAAAAAAAAA"; + size_t data_len = strlen(data); + struct bytes encrypted = + mt19937_encrypt_decrypt(key, (unsigned char *)data, data_len); + struct bytes decrypted = + mt19937_encrypt_decrypt(key, encrypted.data, encrypted.len); + assert(!memcmp(decrypted.data, (unsigned char *)data, data_len)); + encrypted = prefix_encrypt((unsigned char *)data, data_len); + size_t plaintext_start = encrypted.len - data_len; + uint16_t guess; + for (guess = 0; guess < 0xFFFF; guess++) { + struct bytes candidate = + mt19937_encrypt_decrypt(guess, encrypted.data, encrypted.len); + if (!memcmp(candidate.data + plaintext_start, data, data_len)) + break; + } + assert(guess == rand_key); + printf("Key 0x%04" PRIX16 " recovered\n", guess); + return 0; +}