Alok Menghrajani

Security engineer at Square. Previously co-author of Hacklang and pushed for adoption of 100% https at Facebook.

Home | Contact me | Github | Twitter | Facebook

A minor WTF in openssl, where mixing encryption & decryption functions does not result in an error. This can lead to subtle security bugs if the code is poorly written.

This pull request to address this issue has been open for over a year; it seems nobody cares?

A sane library should fail at the EVP_DecryptUpdate on line 123. Running this code (openssl 1.0.1) however results in:

Encrypting:
  assertion ok: EVP_EncryptInit_ex
  assertion ok: EVP_EncryptUpdate
  assertion ok: EVP_EncryptFinal_ex
  assertion ok: check ct1 len
cipher text: eb87ad64ea2696370c99e6e7

Decrypting:
  assertion ok: EVP_DecryptInit_ex
  assertion ok: EVP_DecryptUpdate
  assertion ok: EVP_DecryptFinal_ex
  assertion ok: check pt2 len
  assertion ok: pt2 == pt1
plain text: hello world

Decrypting a subset, with EVP_EncryptInit_ex
  assertion ok: EVP_EncryptInit_ex
  assertion ok: EVP_DecryptUpdate
  assertion ok: EVP_DecryptFinal_ex
plain text: hello worl

Decrypting a subset, with EVP_DecryptInit_ex
  assertion ok: EVP_DecryptInit_ex
  assertion ok: EVP_DecryptUpdate
  ASSERTION FAILURE: EVP_DecryptFinal_ex
plain text: hello worl

openssl_wtf.c:

/**
 * openssl's EVP library does not prevent you from mixing
 * encryption & decryption functions on a given ctx. This
 * can lead to subtle bugs if the code is poorly written.
 * Data will get decrypted but not authenticated!
 *
 * Compile and run with:
 * gcc -o openssl_wtf openssl_wtf.c
 *            -L/usr/lib/x86_64-linux-gnu/ -lssl -lcrypto
 * ./openssl_wtf
 */
#include <stdio.h>
#include <strings.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/rand.h>

void assert(int exp, char *msg) {
  if (!exp) {
    printf("  ASSERTION FAILURE: %s\n", msg);
  } else {
    printf("  assertion ok: %s\n", msg);
  }
}

int main(int argc, char **argv) {
  unsigned char *key = (unsigned char *)"blah";
 
  unsigned char iv[12] = { 0x00 };
  unsigned char tag[16] = { 0x00 };
  unsigned char pt1[] =
    {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0};
  unsigned char ct1[sizeof(pt1)];
  unsigned char ct2[sizeof(pt1) - 2];
  unsigned char pt2[sizeof(pt1)];
 
  EVP_CIPHER_CTX ctx;
  int ct_offset, pt2_offset, t, r;
 
  EVP_CIPHER_CTX_init(&ctx);
 
  /* Initialize IV, don't use pseudo_bytes in real code */
  RAND_pseudo_bytes(iv, sizeof(iv));

  /* Encrypt the plain text */
  printf("Encrypting:\n");
  bzero(ct1, sizeof(ct1));
  r = EVP_EncryptInit_ex(&ctx, EVP_aes_256_gcm(), NULL, key, iv);
  assert(r == 1, "EVP_EncryptInit_ex");
 
  ct_offset = 0;
 
  r = EVP_EncryptUpdate(&ctx, ct1 + ct_offset, &t, pt1, sizeof(pt1));
  ct_offset += t;
  assert(r == 1, "EVP_EncryptUpdate");
 
  r = EVP_EncryptFinal_ex(&ctx, ct1 + ct_offset, &t);
  ct_offset += t;
  assert(r == 1, "EVP_EncryptFinal_ex");
 
  assert(ct_offset == sizeof(ct1), "check ct1 len");
 
  /* Save tag */
  EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag);

  /* Clean up */
  EVP_CIPHER_CTX_cleanup(&ctx);
 
  printf("cipher text: ");
  for (t=0; t<sizeof(ct1); t++) {
    printf("%02x", ct1[t]);
  }
  printf("\n\n");
 
  /* Decrypt the cipher text */
  printf("Decrypting:\n");
  bzero(pt2, sizeof(pt1));
  EVP_CIPHER_CTX_init(&ctx);
 
  r = EVP_DecryptInit_ex(&ctx, EVP_aes_256_gcm(), NULL, key, iv);
  assert(r == 1, "EVP_DecryptInit_ex");
 
  EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_TAG, sizeof(tag), tag);
 
  pt2_offset = 0;
 
  r = EVP_DecryptUpdate(&ctx, pt2 + pt2_offset, &t, ct1, sizeof(ct1));
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptUpdate");
 
  r = EVP_DecryptFinal_ex(&ctx, pt2 + pt2_offset, &t);
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptFinal_ex");
 
  assert(pt2_offset == sizeof(pt1), "check pt2 len");
 
  r = 1;
  for (t=0; t<sizeof(pt1); t++) {
    r = r && (pt1[t] == pt2[t]);
  }
  assert(r == 1, "pt2 == pt1");
  printf("plain text: %s\n", pt2);  
  EVP_CIPHER_CTX_cleanup(&ctx);
  printf("\n"); 
 
  /* Truncate the cipher text */
  for (t=0; t<sizeof(ct2); t++) {
    ct2[t] = ct1[t];
  }

  /* Decrypt a subset */
  printf("Decrypting a subset, with EVP_EncryptInit_ex\n");
  bzero(pt2, sizeof(pt2));
  EVP_CIPHER_CTX_init(&ctx);
 
  r = EVP_EncryptInit_ex(&ctx, EVP_aes_256_gcm(), NULL, key, iv);
  assert(r == 1, "EVP_EncryptInit_ex");
 
  EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_TAG, sizeof(tag), tag);
 
  pt2_offset = 0;
 
  r = EVP_DecryptUpdate(&ctx, pt2 + pt2_offset, &t, ct2, sizeof(ct2));
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptUpdate");
 
  r = EVP_DecryptFinal_ex(&ctx, pt2 + pt2_offset, &t);
  pt2_offset += t; 
  assert(r == 1, "EVP_DecryptFinal_ex");
  printf("plain text: %s\n", pt2);
  EVP_CIPHER_CTX_cleanup(&ctx);
  printf("\n");
 
 
  /* Decrypt a subset, correctly this time */
  printf("Decrypting a subset, with EVP_DecryptInit_ex\n");
  bzero(pt2, sizeof(pt2));
  EVP_CIPHER_CTX_init(&ctx);
 
  r = EVP_DecryptInit_ex(&ctx, EVP_aes_256_gcm(), NULL, key, iv);
  assert(r == 1, "EVP_DecryptInit_ex");
 
  EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_TAG, sizeof(tag), tag);
 
  pt2_offset = 0;
 
  r = EVP_DecryptUpdate(&ctx, pt2 + pt2_offset, &t, ct2, sizeof(ct2));
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptUpdate");
 
  r = EVP_DecryptFinal_ex(&ctx, pt2 + pt2_offset, &t);
  pt2_offset += t;
  assert(r == 1, "EVP_DecryptFinal_ex");
  printf("plain text: %s\n", pt2); 
  EVP_CIPHER_CTX_cleanup(&ctx);
}