AES CTR 256 Encryption Mode of operation on OpenSSL
Asked Answered
A

2

22

Im new to OpenSSL, Can anybody give me a hint in how to initialize AES CTR mode from a C file. I know this is the method´s signature but I am having problems with the parameters, there´s not many documentation neither a clear example how to make a simple encryption. I would appreciate if somebody could exemplify a call to this method. Thanks in advance!

void AES_ctr128_encrypt(const unsigned char *in, unsigned char *out,
    const unsigned long length, const AES_KEY *key,
    unsigned char ivec[AES_BLOCK_SIZE],
    unsigned char ecount_buf[AES_BLOCK_SIZE],
    unsigned int *num);

Hi Caf I really appreciate your quick answer it has been really useful, and defenetly the best example I have found on the web. I am trying to open a file with undetermined length, encrypt it and write another file with the ciphertext generated, then open the ciphered file and recover the plaintext. I need to use a file of a considerable amount of MB cause I would like to benchmark the performance of the CPU. However Im still having a problem while decrypting. Somehow when decrypting a considerable txt files (1504KB)it wont decrypt it complete, and I get half of it in plaintext and the other half still ciphered. I think this might be related to the size of the iv or the way I am calling the counter. Here is what I have so far:

#include <openssl/aes.h>
#include <stdio.h>
#include <string.h>

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

FILE *fp;
FILE *rp;
FILE *op;
size_t count;   
char * buffer; 
AES_KEY key; 

int bytes_read, bytes_written;   
unsigned char indata[AES_BLOCK_SIZE]; 
unsigned char outdata[AES_BLOCK_SIZE];  
unsigned char ckey[] =  "thiskeyisverybad"; // It is 128bits though..
unsigned char iv[8] = {0};//This should be generated by RAND_Bytes I will take into    consideration your previous post
struct ctr_state state;   

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

void encrypt(){ 
  //Opening files where text plain text is read and ciphertext stored      
  fp=fopen("input.txt","a+b");
  op=fopen("output.txt","w");
  if (fp==NULL) {fputs ("File error",stderr); exit (1);}   
  if (op==NULL) {fputs ("File error",stderr); exit (1);}      

  //Initializing the encryption KEY
  AES_set_encrypt_key(ckey, 128, &key); 

  //Encrypting Blocks of 16 bytes and writing the output.txt with ciphertext  
 while (1) {     
    init_ctr(&state, iv); //Counter call
    bytes_read = fread(indata, 1, AES_BLOCK_SIZE, fp); 
    AES_ctr128_encrypt(indata, outdata, bytes_read, &key, state.ivec, state.ecount, &state.num);    
    bytes_written = fwrite(outdata, 1, bytes_read, op); 
    if (bytes_read < AES_BLOCK_SIZE) 
    break; 
  }   

  fclose (fp); 
  fclose (op);
  free (buffer); 
}

void decrypt(){
  //Opening files where text cipher text is read and the plaintext recovered         
  rp=fopen("recovered.txt","w");
  op=fopen("output.txt","a+b");
  if (rp==NULL) {fputs ("File error",stderr); exit (1);}   
  if (op==NULL) {fputs ("File error",stderr); exit (1);} 

  //Initializing the encryption KEY
  AES_set_encrypt_key(ckey, 128, &key); 

  //Encrypting Blocks of 16 bytes and writing the output.txt with ciphertext   
  while (1) {     
    init_ctr(&state, iv);//Counter call
    bytes_read = fread(indata, 1, AES_BLOCK_SIZE, op);  
    AES_ctr128_encrypt(indata, outdata, bytes_read, &key, state.ivec, state.ecount, &state.num); 
    bytes_written = fwrite(outdata, 1, bytes_read, rp); 
    if (bytes_read < AES_BLOCK_SIZE) 
    break; 
    }   
  fclose (rp); 
  fclose (op);
  free (buffer); 
}

int main(int argc, char *argv[]){  
  encrypt();  
  //decrypt(); 
  system("PAUSE");  
  return 0;
}

Each encrypt and decrypt function are called in different runs so everything is initialized always with the same values. Thanks again for the hints you can provide me in advance & Regards!!!

Auricular answered 29/6, 2010 at 14:50 Comment(3)
Your problem is that you are reinitialising the counter after each block. This is wrong - move the init_ctr() call outside of the while() loops in both encryption and decryption. indata and outdata also don't have to be of AES_BLOCK_SIZE length - they can be considerably larger.Countess
You should not use AES_encrypt and friends. That's a software-only implementation, so you will not enjoy hardware support, like AES-NI. You should be using EVP_* functions. See EVP Symmetric Encryption and Decryption on the OpenSSL wiki. In fact, you should probably be using authenticated encryption because it provides both confidentiality and authenticity. See EVP Authenticated Encryption and Decryption on the OpenSSL wiki.Pancho
If you use the EVP_* functions, then the ciphers of interest are EVP_aes_128_ctr, EVP_aes_192_ctr and EVP_aes_256_ctr.Pancho
C
30

Usually, you will be intending to call AES_ctr128_encrypt() repeatedly to send several messages with the same key and IV, and an incrementing counter. This means you need to keep track of the 'ivec', 'num' and 'ecount' values between calls - so create a struct to hold these, and an initialisation function:

struct ctr_state {
    unsigned char ivec[16];  /* ivec[0..7] is the IV, ivec[8..15] is the big-endian counter */
    unsigned int num;
    unsigned char ecount[16];
};

int init_ctr(struct ctr_state *state, const unsigned char iv[8])
{
    /* aes_ctr128_encrypt requires 'num' and 'ecount' set to zero on the
     * first call. */
    state->num = 0;
    memset(state->ecount, 0, 16);

    /* Initialise counter in 'ivec' to 0 */
    memset(state->ivec + 8, 0, 8);

    /* Copy IV into 'ivec' */
    memcpy(state->ivec, iv, 8);
}

Now, when you start communicating with the destination, you'll need to generate an IV to use and initialise the counter:

unsigned char iv[8];
struct ctr_state state;

if (!RAND_bytes(iv, 8))
    /* Handle the error */;

init_ctr(&state, iv);

You will then need to send the 8 byte IV to the destination. You'll also need to initialise an AES_KEY from your raw key bytes:

AES_KEY aes_key;

if (AES_set_encrypt_key(key, 128, &aes_key))
    /* Handle the error */;

You can now start encrypting data and sending it to the destination, with repeated calls to AES_ctr128_encrypt() like this:

AES_ctr128_encrypt(msg_in, msg_out, msg_len, &aes_key, state->ivec, state->ecount, &state->num);

(msg_in is a pointer to a buffer containing the plaintext message, msg_out is a pointer to a buffer where the encrypted message should go, and msg_len is the message length).

Decryption is exactly the same, except that you do not generate the IV with RAND_bytes() - instead, you take the value given to you by the other side.

Important:

  1. Do not call init_ctr() more than once during the encryption process. The counter and IV must be initialised once only prior to the start of encryption.

  2. Under no circumstances be tempted to get the IV anywhere other than from RAND_bytes() on the encryption side. Don't set it to a fixed value; don't use a hash function; don't use the recipient's name; don't read it from disk. Generate it with RAND_bytes() and send it to the destination. Whenever you start with a zero counter, you must start with a completely fresh IV that you have never used before.

  3. If it is at all possible that you will be sending 2**64 bytes without changing the IV and/or key, you will need to test for the counter overflowing.

  4. Do not omit error-checking. If a function fails and you ignore it, it's quite possible (even likely) that your system will appear to be functioning normally, but will actually be operating completely insecurely.

Countess answered 30/6, 2010 at 3:9 Comment(3)
Let me add a detail that escaped me when I used this: the num argument is how many bytes into a block you are, not the counter. If you are encrypting packets (for instance), always set state->num to zero and put your counter into the high bytes of iv.Vernievernier
@Mike Elkins: Indeed - you can treat both num and ecount as opaque internal state of the OpenSSL CTR implementation. In most cases it should not be necessary to directly alter them.Countess
if (!AES_set_encrypt_key(key, 128, &aes_key)) /* Handle the error */ should instead be if (AES_set_encrypt_key(key, 128, &aes_key)) /* Handle the error */ because AES_set_encrypt_key returns 0 on success, see man.openbsd.org/AES_encrypt.3#RETURN_VALUESSavina
S
3

It looks like the basic problem with your test program is that the mode values of the fopen calls is not correct. I think you need to change your fopen calls in encrypt to this:

fp=fopen("input.txt","rb");
op=fopen("output.txt","wb");

And the ones in decrypt to:

rp=fopen("recovered.txt","wb");
op=fopen("output.txt","rb");

One other thing worth pointing out is that ckey should probably be declared as a 32 byte (256 bit) buffer. It is true that the 128-bit encryption only uses 16 bytes of the data from the key. But the OpenSSL function AES_set_encrypt_key (at least in the version I am using) reads 32 bytes from that buffer. It only uses the appropriate number of bytes, but the read does occur. That means that if the buffer is only 16-bytes and happens end at the end of a page that is adjacent to a non-readable page in memory, it would result in an access violation.

Oh - and I just noticed that there is an extraneous call to free in there. The free(buffer); call is not valid since buffer was never allocated. I realize your code is just a simple test, but ... well, we are programmers and can't help ourselves.

Shoulders answered 5/8, 2010 at 16:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.