Search code examples
cencryptionopensslaesencryption-symmetric

How to convert AES_encrypt in counter mode to EVP interfaces?


I'm in the process of coding some stuff around common-encryption which require AES Ctr 128. So I'm digging a bit with crypto stuff.

Currently I test a code (find here) which work (encrypt/decrypt a file):

#include <openssl/aes.h>
#include <openssl/rand.h>
#include <openssl/hmac.h>
#include <openssl/buffer.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>


struct ctr_state {
  unsigned char ivec[AES_BLOCK_SIZE];
  unsigned int num;
  unsigned char ecount[AES_BLOCK_SIZE];
};

void print_hex(unsigned char *c) {
  for(int i = 0; i < 16; i++) {
    printf("%02X.", c[i]);
  }
  printf("\n");
}


void init_ctr(struct ctr_state *state, const unsigned char iv[16]) {
  state->num = 0;
  memset(state->ecount, 0, 16);
  memset(state->ivec + 8, 0, 8);
  memcpy(state->ivec, iv, 8);
}

void fencrypt(char* read, char* write, const unsigned char* enc_key) {

  FILE *readFile;
  FILE *writeFile;
  AES_KEY key;

  int bytes_read;
  unsigned char indata[AES_BLOCK_SIZE];
  unsigned char outdata[AES_BLOCK_SIZE];
  unsigned char iv[AES_BLOCK_SIZE];

  struct ctr_state state;


  RAND_bytes(iv, AES_BLOCK_SIZE);

  print_hex(iv);
  readFile = fopen(read,"rb");
  writeFile = fopen(write,"wb");


  AES_set_encrypt_key(enc_key, 128, &key);

  init_ctr(&state, iv);

  fwrite(state.ivec, 1, AES_BLOCK_SIZE, writeFile);

  print_hex(state.ivec);

  while(1) {
    bytes_read = fread(indata, 1, AES_BLOCK_SIZE, readFile);
    AES_ctr128_encrypt(indata, outdata, bytes_read, &key, state.ivec, state.ecount, &state.num);

    print_hex(state.ivec);
    fwrite(outdata, 1, bytes_read, writeFile);
    if (bytes_read < AES_BLOCK_SIZE) {
      break;
    }
  }

  fclose(writeFile);
  fclose(readFile);
}

void fdecrypt(char* read, char* write, const unsigned char* enc_key) {

  FILE *readFile;
  FILE *writeFile;
  AES_KEY key;

  int bytes_read;
  unsigned char indata[AES_BLOCK_SIZE];
  unsigned char outdata[AES_BLOCK_SIZE];
  unsigned char iv[AES_BLOCK_SIZE];

  struct ctr_state state;

  readFile=fopen(read,"rb");
  writeFile=fopen(write,"wb");

  fread(iv, 1, AES_BLOCK_SIZE, readFile);

  AES_set_encrypt_key(enc_key, 128, &key);

  init_ctr(&state, iv);

  while(1) {
    bytes_read = fread(indata, 1, AES_BLOCK_SIZE, readFile);
    AES_ctr128_encrypt(indata, outdata, bytes_read, &key, state.ivec, state.ecount, &state.num);

    print_hex(state.ivec);
    fwrite(outdata, 1, bytes_read, writeFile);
    if (bytes_read < AES_BLOCK_SIZE) {
            break;
    }
  }
  fclose(writeFile);
  fclose(readFile);
}

int main(int argc, char *argv[]) {
 char* secret = "supersecret";

 fencrypt("encme.txt", "enced.enc", (const unsigned char*)secret);
 fdecrypt("enced.enc", "unenced.txt", (const unsigned char*)secret);
}

This work well. But It seems that the standard is to to use EVP functions now with openssl. So I try to adapt my code but something is clearly wrong with my implementations. I don't understand on how properly update/increment the IV vector.

Here my new code with EVP (work but not increment/counter):

#include <openssl/aes.h>
#include <openssl/rand.h>
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


struct ctr_state {
  EVP_CIPHER_CTX* cipher;
  int num;
};

void print_hex(unsigned char *c) {
  for(int i = 0; i < 16; i++) {
    printf("%02X.", c[i]);
  }
  printf("\n");
}

void init_ctr(struct ctr_state *state, unsigned char iv[16], unsigned char* key) {
    state->num = 0;
    state->cipher = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex(state->cipher, EVP_aes_128_ctr(), NULL, key, iv);
}

void fencrypt(char* read, char* write, unsigned char* enc_key) {

  FILE *readFile;
  FILE *writeFile;

  int bytes_read;
  unsigned char indata[AES_BLOCK_SIZE];
  unsigned char outdata[AES_BLOCK_SIZE];
  unsigned char iv[AES_BLOCK_SIZE];

  struct ctr_state state;

  RAND_bytes(iv, AES_BLOCK_SIZE);

  readFile = fopen(read,"rb");
  writeFile = fopen(write,"wb");

  fwrite(iv, 1, AES_BLOCK_SIZE, writeFile);
  init_ctr(&state, iv, enc_key);
  print_hex(iv);

  while(1) {
    bytes_read = fread(indata, 1, AES_BLOCK_SIZE, readFile);

    EVP_EncryptUpdate(state.cipher, outdata, &state.num, indata, bytes_read);

    EVP_EncryptUpdate(state.cipher, outdata, &state.num, indata, bytes_read);

    fwrite(outdata, 1, bytes_read, writeFile);
    if (bytes_read < AES_BLOCK_SIZE) {
      break;
    }
  }

  fclose(writeFile);
  fclose(readFile);
}

void fdecrypt(char* read, char* write, unsigned char* enc_key) {

  FILE *readFile;
  FILE *writeFile;

  int bytes_read;
  unsigned char indata[AES_BLOCK_SIZE];
  unsigned char outdata[AES_BLOCK_SIZE];
  unsigned char iv[AES_BLOCK_SIZE];

  struct ctr_state state;

  readFile = fopen(read,"rb");
  writeFile = fopen(write,"wb");

  fread(iv, 1, AES_BLOCK_SIZE, readFile);
  init_ctr(&state, iv, enc_key);

  print_hex(iv);

  while(1) {
    bytes_read = fread(indata, 1, AES_BLOCK_SIZE, readFile);

    EVP_EncryptUpdate(state.cipher, outdata, &state.num, indata, bytes_read);
    printf("Pass %d ",state.num);

    fwrite(outdata, 1, bytes_read, writeFile);
    if (bytes_read < AES_BLOCK_SIZE) {
      break;
    }
  }

  fclose(writeFile);
  fclose(readFile);
}

int main(int argc, char *argv[]) {
 char* secret = "supersecret";

 fencrypt("encme.txt", "enced.enc", (unsigned char*)secret);
 fdecrypt("enced.enc", "unenced.txt", (unsigned char*)secret);
}

Any help appreciated. Thank you.


Solution

  • OK I think I got it.

    I will copy here my two program example:

    AES_CTR_128 (without EVP) :

    #include <openssl/aes.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct ctr_state {
      unsigned int num;
      unsigned char ivec[AES_BLOCK_SIZE];
      unsigned char ecount[AES_BLOCK_SIZE];
    };
    
    void init_ctr(struct ctr_state *state, const unsigned char iv[16]) {
      state->num = 0;
      memset(state->ecount, 0, 16);
      memset(state->ivec + 8, 0, 8);
      memcpy(state->ivec, iv, 8);
    }
    
    void fencrypt(char* read, char* write, const unsigned char* enc_key) {
    
      FILE *readFile;
      FILE *writeFile;
      AES_KEY key;
    
      int bytes_read;
      unsigned char indata[AES_BLOCK_SIZE];
      unsigned char outdata[AES_BLOCK_SIZE];
      struct ctr_state state;
      unsigned char *iv = (unsigned char *)"0123456789012345";
    
      readFile = fopen(read,"rb");
      writeFile = fopen(write,"wb");
    
      fwrite(iv, 1, AES_BLOCK_SIZE, writeFile);
    
      AES_set_encrypt_key(enc_key, 128, &key);
      init_ctr(&state, iv);
    
      while(1) {
        bytes_read = fread(indata, 1, AES_BLOCK_SIZE, readFile);
    
        AES_ctr128_encrypt(indata, outdata, bytes_read, &key, state.ivec, state.ecount, &state.num);
    
        fwrite(outdata, 1, bytes_read, writeFile);
        if (bytes_read < AES_BLOCK_SIZE) {
          break;
        }
      }
    
      fclose(writeFile);
      fclose(readFile);
    }
    
    void fdecrypt(char* read, char* write, const unsigned char* enc_key) {
    
      FILE *readFile;
      FILE *writeFile;
      AES_KEY key;
    
      int bytes_read;
      unsigned char indata[AES_BLOCK_SIZE];
      unsigned char outdata[AES_BLOCK_SIZE];
      unsigned char iv[AES_BLOCK_SIZE];
      struct ctr_state state;
    
      readFile=fopen(read,"rb");
      writeFile=fopen(write,"wb");
    
      fread(iv, 1, AES_BLOCK_SIZE, readFile);
    
      AES_set_encrypt_key(enc_key, 128, &key);
      init_ctr(&state, iv);
    
      while(1) {
        bytes_read = fread(indata, 1, AES_BLOCK_SIZE, readFile);
        AES_ctr128_encrypt(indata, outdata, bytes_read, &key, state.ivec, state.ecount, &state.num);
    
        fwrite(outdata, 1, bytes_read, writeFile);
        if (bytes_read < AES_BLOCK_SIZE) {
          break;
        }
      }
    
      fclose(writeFile);
      fclose(readFile);
    }
    
    int main(int argc, char *argv[]) {
     unsigned char *secret = (unsigned char *)"0123456789012345";
    
     fencrypt("encme.txt", "enced.enc", secret);
     fdecrypt("enced.enc", "unenced.txt", secret);
    }
    

    Everything classic as in another example. The IV(or nonce) is constant to make the debugging easier (don't do that).

    And below my code with EVP:

    #include <openssl/aes.h>
    #include <openssl/evp.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    
    struct ctr_state {
      EVP_CIPHER_CTX* cipher;
      unsigned int num;
      unsigned char ivec[AES_BLOCK_SIZE];
      unsigned char ecount[AES_BLOCK_SIZE];
    };
    
    static void AES_ctr128_inc(unsigned char *counter) {
      unsigned char* cur_pos;
    
      for (cur_pos = counter + 15; cur_pos >= counter; cur_pos--) {
        (*cur_pos)++;
        if (*cur_pos != 0) {
          break;
        }
      }
    }
    
    void AES_ctr128_EVPencrypt(EVP_CIPHER_CTX* cipher, const unsigned char *in, unsigned char *out,
      const unsigned long length,
      unsigned char counter[AES_BLOCK_SIZE],
      unsigned char ecount_buf[AES_BLOCK_SIZE],
      unsigned int *num) {
    
      int nb;
      unsigned int n;
      unsigned long l=length;
    
      n = *num;
    
      while (l--) {
        if (n == 0) {
          EVP_EncryptUpdate(cipher, ecount_buf, &nb, counter, AES_BLOCK_SIZE);
          AES_ctr128_inc(counter);
        }
        *(out++) = *(in++) ^ ecount_buf[n];
        n = (n+1) % AES_BLOCK_SIZE;
      }
    
      *num=n;
    }
    
    void init_ctr(struct ctr_state *state, unsigned char iv[16], unsigned char* key) {
      state->num = 0;
      memset(state->ecount, 0, 16);
      memset(state->ivec + 8, 0, 8);
      memcpy(state->ivec, iv, 8);
      state->cipher = EVP_CIPHER_CTX_new();
      EVP_EncryptInit_ex(state->cipher, EVP_aes_128_ecb(), NULL, key, NULL);
    }
    
    void fencrypt(char* read, char* write, unsigned char* enc_key) {
    
      FILE *readFile;
      FILE *writeFile;
    
      int bytes_read;
      unsigned char indata[AES_BLOCK_SIZE];
      unsigned char outdata[AES_BLOCK_SIZE];
      struct ctr_state state;
      unsigned char *iv = (unsigned char *)"0123456789012345";
    
      readFile = fopen(read,"rb");
      writeFile = fopen(write,"wb");
    
      fwrite(iv, 1, AES_BLOCK_SIZE, writeFile);
    
      init_ctr(&state, iv, enc_key);
    
      while(1) {
        bytes_read = fread(indata, 1, AES_BLOCK_SIZE, readFile);
    
        AES_ctr128_EVPencrypt(state.cipher, indata, outdata, bytes_read, state.ivec, state.ecount, &state.num);
    
        fwrite(outdata, 1, bytes_read, writeFile);
        if (bytes_read < AES_BLOCK_SIZE) {
          break;
        }
      }
    
      fclose(writeFile);
      fclose(readFile);
    }
    
    void fdecrypt(char* read, char* write, unsigned char* enc_key) {
    
      FILE *readFile;
      FILE *writeFile;
    
      int bytes_read;
      unsigned char indata[AES_BLOCK_SIZE];
      unsigned char outdata[AES_BLOCK_SIZE];
      unsigned char iv[AES_BLOCK_SIZE];
      struct ctr_state state;
    
      readFile = fopen(read,"rb");
      writeFile = fopen(write,"wb");
    
      fread(iv, 1, AES_BLOCK_SIZE, readFile);
    
      init_ctr(&state, iv, enc_key);
    
      while(1) {
        bytes_read = fread(indata, 1, AES_BLOCK_SIZE, readFile);
    
        AES_ctr128_EVPencrypt(state.cipher, indata, outdata, bytes_read, state.ivec, state.ecount, &state.num);
    
        fwrite(outdata, 1, bytes_read, writeFile);
        if (bytes_read < AES_BLOCK_SIZE) {
          break;
        }
      }
    
      fclose(writeFile);
      fclose(readFile);
    }
    
    int main(int argc, char *argv[]) {
     unsigned char *secret = (unsigned char *)"0123456789012345";
    
     fencrypt("encme.txt", "enced.enc", (unsigned char*)secret);
     fdecrypt("enced.enc", "unenced.txt", secret);
    }
    

    So I basically copy the AES_ctr_encrypt function to use EVP, and adapt it.

    It work for me, as I can use both implementation to encrypt/decrypt the same program.

    Comments are welcome. Questions remain that what do the aes_ctr_128 in EVP ? and how to use it? I think I have reinvented the wheel.