10

What I am trying to do: Write a program in C that opens a file of arbitrary size and reads its contents. Once The contents are read it will encrypt them in AES 256 CBC and save the ciphertext to a file called ciphertext. Once this is saved it will close both files. Then will open the cipher text from the file that was just saved and decrypt the cipher text and save it to a file called decrypted.

My Problem: It seems to never decrypt my cipher text. I get garbage, I have no idea what I am doing wrong. Please help.

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

void encrypt(FILE *ifp, FILE *ofp)
{
  //Get file size
  fseek(ifp, 0L, SEEK_END);
  int fsize = ftell(ifp);
  //set back to normal
  fseek(ifp, 0L, SEEK_SET);

  int outLen1 = 0; int outLen2 = 0;
  unsigned char *indata = malloc(fsize);
  unsigned char *outdata = malloc(fsize*2);
  unsigned char ckey[] =  "thiskeyisverybad";
  unsigned char ivec[] = "dontusethisinput";

  //Read File
  fread(indata,sizeof(char),fsize, ifp);//Read Entire File

  //Set up encryption
  EVP_CIPHER_CTX ctx;
  EVP_EncryptInit(&ctx,EVP_aes_256_cbc(),ckey,ivec);
  EVP_EncryptUpdate(&ctx,outdata,&outLen1,indata,fsize);
  EVP_EncryptFinal(&ctx,outdata,&outLen2);
  fwrite(outdata,sizeof(char),fsize,ofp);
}

void decrypt(FILE *ifp, FILE *ofp)
{
  //Get file size
  fseek(ifp, 0L, SEEK_END);
  int fsize = ftell(ifp);
  //set back to normal
  fseek(ifp, 0L, SEEK_SET);

  int outLen1 = 0; int outLen2 = 0;
  unsigned char *indata = malloc(fsize);
  unsigned char *outdata = malloc(fsize*2);
  unsigned char ckey[] =  "thiskeyisverybad";
  unsigned char ivec[] = "dontusethisinput";

  //Read File
  fread(indata,sizeof(char),fsize, ifp);//Read Entire File

  //setup decryption
  EVP_CIPHER_CTX ctx;
  EVP_DecryptInit(&ctx,EVP_aes_256_cbc(),ckey,ivec);
  EVP_DecryptUpdate(&ctx,outdata,&outLen1,indata,fsize);
  EVP_DecryptFinal(&ctx,outdata,&outLen2);
  fwrite(outdata,sizeof(char),fsize,ofp);
}

int main(int argc, char *argv[])
{    
  FILE *fIN, *fOUT;

  fIN = fopen("plain.txt", "rb");//File to be encrypted; plain text
  fOUT = fopen("cyphertext.txt", "wb");//File to be written; cipher text    
  encrypt(fIN, fOUT);

  fclose(fIN);
  fclose(fOUT);

  //Decrypt file now
  fIN = fopen("cyphertext.txt", "rb");//File to be written; cipher text
  fOUT = fopen("decrypted.txt", "wb");//File to be written; cipher text
  decrypt(fIN,fOUT);

  fclose(fIN);
  fclose(fOUT);

  return 0;
}

Note: there may be some misspellings. EDIT: Seems Like I made a mistake with the key and IV, both of which are 128 bit and I am trying to use the 256 bit CBC. This was my problem, seems to work once I changed it to

EVP_aes_128_cbc()
jww
  • 97,681
  • 90
  • 411
  • 885
Kevin
  • 3,077
  • 6
  • 31
  • 77
  • Your assumption that the length of the output file is the same as the length of the input is wrong. Also, are you certain your C compiler supports dynamic arrays? Normally you'd have to `malloc` indata and outdata. – President James K. Polk Jul 21 '14 at 02:06
  • okay, so If I were to say make the output file at least 2 times the size of the input I will be safe? or is there a better way? also in terms of the dynamic array, I am not sure that my compiler does (code blocks default, should be GCC). Regardless I did a malloc(fsize) and malloc(fsize*2). It still gives me garbage data. – Kevin Jul 21 '14 at 02:18
  • Your EVP_*Update() methods are overwriting the beginning of the outdata array. You need something like `EVP_EncryptFinal(&ctx,outdata + outLen1,&outLen2);`. Same thing for Decyption. – President James K. Polk Jul 21 '14 at 02:46
  • Not entirely sure how that works, isn't the second argument unsigned char * and if I add an int how does that prevent the methods from overwriting? – Kevin Jul 21 '14 at 02:52
  • Thanks GregS got my answer. Although I am still unsure why that trick works, but it does. – Kevin Jul 21 '14 at 03:23
  • `unsigned char *outdata = malloc(fsize*2);` - you can change that to `unsigned char *outdata = malloc(fsize + 16)`. You only need enough space for padding, and that's *at most* one block size. – jww Jul 24 '14 at 16:12

2 Answers2

13

Here is my version of your code. Naturally I like it better, but I offer it just as an alternative. Note the complete absence of error checking: real code would have it.

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

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif


/**
 * Encrypt or decrypt, depending on flag 'should_encrypt'
 */
void en_de_crypt(int should_encrypt, FILE *ifp, FILE *ofp, unsigned char *ckey, unsigned char *ivec) {

    const unsigned BUFSIZE=4096;
    unsigned char *read_buf = malloc(BUFSIZE);
    unsigned char *cipher_buf;
    unsigned blocksize;
    int out_len;
    EVP_CIPHER_CTX ctx;

    EVP_CipherInit(&ctx, EVP_aes_256_cbc(), ckey, ivec, should_encrypt);
    blocksize = EVP_CIPHER_CTX_block_size(&ctx);
    cipher_buf = malloc(BUFSIZE + blocksize);

    while (1) {

        // Read in data in blocks until EOF. Update the ciphering with each read.

        int numRead = fread(read_buf, sizeof(unsigned char), BUFSIZE, ifp);
        EVP_CipherUpdate(&ctx, cipher_buf, &out_len, read_buf, numRead);
        fwrite(cipher_buf, sizeof(unsigned char), out_len, ofp);
        if (numRead < BUFSIZE) { // EOF
            break;
        }
    }

    // Now cipher the final block and write it out.

    EVP_CipherFinal(&ctx, cipher_buf, &out_len);
    fwrite(cipher_buf, sizeof(unsigned char), out_len, ofp);

    // Free memory

    free(cipher_buf);
    free(read_buf);
}

int main(int argc, char *argv[]) {

    unsigned char ckey[] = "thiskeyisverybad";
    unsigned char ivec[] = "dontusethisinput";
    FILE *fIN, *fOUT;

    if (argc != 2) {
        printf("Usage: <executable> /path/to/file/exe");
        return -1;
    }

    // First encrypt the file

    fIN = fopen("plain.txt", "rb"); //File to be encrypted; plain text
    fOUT = fopen("cyphertext.txt", "wb"); //File to be written; cipher text

    en_de_crypt(TRUE, fIN, fOUT, ckey, ivec);

    fclose(fIN);
    fclose(fOUT);

    //Decrypt file now

    fIN = fopen("cyphertext.txt", "rb"); //File to be read; cipher text
    fOUT = fopen("decrypted.txt", "wb"); //File to be written; cipher text

    en_de_crypt(FALSE, fIN, fOUT, ckey, ivec);

    fclose(fIN);
    fclose(fOUT);

    return 0;
}
President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
  • Why do we read the file in chunks? What if we read past the file and store this into a buffer, then encrypt it. Wouldn't this give us data we don't need. IE if we were trying to encrypt an executable would reading those extra bytes cause a problem in execution of said program after decryption? – Kevin Jul 23 '14 at 13:23
  • I don't understand your question. What "extra bytes" am I reading? fread will never read more bytes than there are left in the file. – President James K. Polk Jul 24 '14 at 00:48
  • This is true, but where my concern is the bytes left over in the buffer. Lets say on the last read, fread reads 4000 bytes (because there are no more than 4000 bytes left to read) and stores it into the buffer "read_buf". We should now have 4000 bytes which are read from the last read and 96 bytes of unused allocated space. we are passing the entire buffer of data from read and unused space into the EVP_CipherUpdate thus encrypting the unused space. Will this cause an issue upon decryption? – Kevin Jul 24 '14 at 13:11
  • 1
    @Kevin: No, its not a problem because the actual number of byte read by `fread` is returned and saved in the variable `numRead`, and then this variable is supplied as the last argument of `EVP_CipherUpdate(...)`. So the unused space in read_buf is not touched by `EVP_CipherUpdate(...)`. – President James K. Polk Jul 24 '14 at 22:18
  • Perfect explanation! I can see why it was done this way. Appreciate it. – Kevin Jul 25 '14 at 12:59
  • The code is fine.. But when I write two files containing the same code, one which encrypts and the other which decrypts using same key, ivec etc.., I am unable to decrypt. Decrypt is working only if I encrypt and decrypt using same file... What can we do to decrypt the ciphertext.txt file later using some other file?? – Technoid Apr 02 '16 at 16:43
  • @JamesKPolk, any reason why `BUFSIZE` was chosen as `4096` and not something else? – learner Oct 04 '17 at 00:19
7

This code works, if anyone has some suggestions as to how it would be cleaner or more efficient please drop a comment.

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

void encrypt(FILE *ifp, FILE *ofp)
{
    //Get file size
    fseek(ifp, 0L, SEEK_END);
    int fsize = ftell(ifp);
    //set back to normal
    fseek(ifp, 0L, SEEK_SET);

    int outLen1 = 0; int outLen2 = 0;
    unsigned char *indata = malloc(fsize);
    unsigned char *outdata = malloc(fsize*2);
    unsigned char ckey[] =  "thiskeyisverybad";
    unsigned char ivec[] = "dontusethisinput";

    //Read File
    fread(indata,sizeof(char),fsize, ifp);//Read Entire File

    //Set up encryption
    EVP_CIPHER_CTX ctx;
    EVP_EncryptInit(&ctx,EVP_aes_128_cbc(),ckey,ivec);
    EVP_EncryptUpdate(&ctx,outdata,&outLen1,indata,fsize);
    EVP_EncryptFinal(&ctx,outdata + outLen1,&outLen2);
    fwrite(outdata,sizeof(char),outLen1 + outLen2,ofp);
}

void decrypt(FILE *ifp, FILE *ofp)
{
    //Get file size
    fseek(ifp, 0L, SEEK_END);
    int fsize = ftell(ifp);
    //set back to normal
    fseek(ifp, 0L, SEEK_SET);

    int outLen1 = 0; int outLen2 = 0;
    unsigned char *indata = malloc(fsize);
    unsigned char *outdata = malloc(fsize);
    unsigned char ckey[] =  "thiskeyisverybad";
    unsigned char ivec[] = "dontusethisinput";

    //Read File
    fread(indata,sizeof(char),fsize, ifp);//Read Entire File

    //setup decryption
    EVP_CIPHER_CTX ctx;
    EVP_DecryptInit(&ctx,EVP_aes_128_cbc(),ckey,ivec);
    EVP_DecryptUpdate(&ctx,outdata,&outLen1,indata,fsize);
    EVP_DecryptFinal(&ctx,outdata + outLen1,&outLen2);
    fwrite(outdata,sizeof(char),outLen1 + outLen2,ofp);
}

int main(int argc, char *argv[])
{        
    if(argc != 2){
        printf("Usage: <executable> /path/to/file/exe");
        return -1;
    }
    FILE *fIN, *fOUT;
    fIN = fopen("plain.txt", "rb");//File to be encrypted; plain text
    fOUT = fopen("cyphertext.txt", "wb");//File to be written; cipher text

    encrypt(fIN, fOUT);
    fclose(fIN);
    fclose(fOUT);
    //Decrypt file now
    fIN = fopen("cyphertext.txt", "rb");//File to be written; cipher text
    fOUT = fopen("decrypted.txt", "wb");//File to be written; cipher text
    decrypt(fIN,fOUT);
    fclose(fIN);
    fclose(fOUT);

    return 0;
}

Also According to this post the EVP api will handle an arbitrary sized input

AES Encryption- large files

Community
  • 1
  • 1
Kevin
  • 3,077
  • 6
  • 31
  • 77
  • Thanks for the feedback. Note that your output buffer for the encrypt operation `*outdata` is rather oversized. Normally you only have to allow for the padding to take place, which means an output buffer size of `fsize + EVP_CIPHER_block_size(ctx)` (which may still be slightly oversized actually, but I guess you can spare an additional ~15 bytes). – Maarten Bodewes Jul 21 '14 at 18:54