cryptopals

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

17_cbc_padding_oracle.c (4355B)


// The CBC padding oracle
#include "aes.h"
#include "base64.h"
#include "blocks.h"
#include "random.h"
#include <assert.h>

/*
 * this is the best-knowwn attach on modern block-cipher cryptography. combine
 * your padding code and your cbc code to write two functions. the first
 * function should select at random one of the 10 strings in [data/17.txt]
 * ...generate a random aes key (which it should save for all future
 * encryption), pad the string out to the 16-byte aes block size, and cbc
 * encrypt it under that key, providing the ciphertext and the iv. the second
 * function should consume the ciphertext produced by the first function,
 * decrypt it, check its padding, and return true or false depending on whether
 * the padding is valid. what you're doing here... this pair of functions
 * approximates aes-cbc encryption as its deployed serverside in web
 * applications; the second function models the server's consumption of an
 * ecrypted session token, as if it was a cookie. it turns out that it's
 * possible to decrypt the ciphertexts provided by the first function. the
 * decryption here depends on a side-channel leak by the decryption function.
 * the leak is the error message that the padding is valid or not. you can find
 * 100 web pages on how this attack works, but the fundamental insight behind
 * this attack is that the byte 01h is valid padding, and occur 1/256 trials of
 * "randomised" plaintexts produced by decrypting a tampered ciphertext. 02h in
 * isolation is not valid padding. 02h 02h is valid padding, but is much less
 * likely to occur randomly than 01h. so you can assume that if you corrupt a
 * decryption and it had valid padding, you know what that padding byte is. it
 * is easy to get tripped up on the fact that cbc plaintexts are "padded".
 * padding oracles have nothing to with the actual padding on a cbc plaintext.
 * it's an attack that targets a specific bit of code that handles decryption.
 * you can mount a padding oracle on any cbc block, whether it's padded or not.
 */

#define LINE_LEN 81

unsigned char *key;
size_t key_len;
unsigned char iv[16] = {0};

bool is_valid_padding(unsigned char *encrypted, size_t encrypted_len) {
  struct bytes decrypted =
      aes_decrypt_cbc((unsigned char *)encrypted, encrypted_len,
                      (const unsigned char *)key, key_len, iv);
  if (!decrypted.data)
    return false;
  free(decrypted.data);
  return true;
}

struct bytes padding_attack(unsigned char *encrypted, size_t encrypted_len,
                            unsigned char *iv) {
  unsigned char **blocks = NULL, *prev_block = iv, attack_block[32] = {0};
  char *plaintext = (char *)malloc(1 + (encrypted_len * sizeof(char)));
  plaintext[encrypted_len] = 0;
  size_t num_blocks = chunk(encrypted, encrypted_len, 16, &blocks);
  for (size_t block = 0; block < num_blocks; block++) {
    memcpy(attack_block + 16, blocks[block], 16);
    for (size_t i = 0; i < 16; i++) {
      char pad_byte = i + 1;
      for (size_t j = 0; j < i; j++)
        attack_block[15 - j] =
            prev_block[15 - j] ^ plaintext[(16 * block) + 15 - j] ^ pad_byte;
      for (int byte = 0; byte < 256; byte++) {
        attack_block[15 - i] = byte;
        if (is_valid_padding(attack_block, sizeof(attack_block))) {
          char decrypted = prev_block[15 - i] ^ byte ^ pad_byte;
          int idx = (16 * block) + 15 - i;
          plaintext[idx] = decrypted;
          break;
        }
      }
    }
    memcpy(prev_block, blocks[block], 16);
  }
  struct bytes res = pkcs7_strip((unsigned char *)plaintext, encrypted_len);
  return res;
}

int main(void) {
  char *decoded = NULL, line[LINE_LEN];
  struct bytes encrypted, decrypted;
  key = random_bytes(16);
  key_len = 16;
  FILE *input = fopen("data/17.txt", "r");
  for (int line_num = 1; fgets(line, LINE_LEN, input); line_num++) {
    int decoded_len = base64_decode(line, strlen(line), &decoded);
    encrypted = aes_encrypt_cbc(decoded, decoded_len,
                                (const unsigned char *)key, key_len, iv);
    if (encrypted.data) {
      decrypted = padding_attack(encrypted.data, encrypted.len, iv);
      printf("%s\n", decrypted.data);
      assert(!strncmp((char *)decrypted.data, decoded, decoded_len));
      free(encrypted.data);
      free(decrypted.data);
    }
  }
  free(decoded);
  return 0;
}