cencryptioncryptographydescbc-mode

DES CBC mode not outputting correctly


I am working on a project in C to implement CBC mode on top of a skeleton code for DES with OpenSSL. We are not allowed to use a function that does the CBC mode automatically, in the sense that we must implement it ourselves. I am getting output but I have result files and my output is not matching up completely with the intended results. I also am stuck on figuring out how to pad the file to ensure all the blocks are of equal size, which is probably one of the reasons why I'm not receiving the correct output. Any help would be appreciated. Here's my modification of the skeleton code so far:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/des.h>
#include <sys/time.h>
#include <unistd.h>

#define ENC 1
#define DEC 0

DES_key_schedule key;

int  append(char*s, size_t size, char c) {
    if(strlen(s) + 1 >= size) {
        return 1;
    }
    int len = strlen(s);
    s[len] = c;
    s[len+1] = '\0';
    return 0;
}

int getSize (char * s) {
    char * t;
    for (t = s; *t != '\0'; t++)
        ;
    return t - s;
}

void strToHex(const_DES_cblock input, unsigned char *output) {
    int arSize = 8;
    unsigned int byte;
    for(int i=0; i<arSize; i++) {
        if(sscanf(input, "%2x", &byte) != 1) {
            break;
        }
        output[i] = byte;
        input += 2;
    }

}

void doBitwiseXor(DES_LONG *xorValue, DES_LONG* data, const_DES_cblock roundOutput) {
    DES_LONG temp[2];
    memcpy(temp, roundOutput, 8*sizeof(unsigned char));
    for(int i=0; i<2; i++) {
        xorValue[i] = temp[i] ^ data[i];
    }
}

void doCBCenc(DES_LONG *data, const_DES_cblock roundOutput, FILE *outFile) {

    DES_LONG in[2];
    doBitwiseXor(in, data, roundOutput);

    DES_encrypt1(in,&key,ENC);

    printf("ENCRYPTED\n");
    printvalueOfDES_LONG(in);

    printf("%s","\n");
    fwrite(in, 8, 1, outFile);

    memcpy(roundOutput, in, 2*sizeof(DES_LONG));
}

int main(int argc, char** argv)
{
        const_DES_cblock cbc_key = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef};
        const_DES_cblock IV = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef};

        // Initialize the timing function
        struct timeval start, end;
        gettimeofday(&start, NULL);

        int l;
        if ((l = DES_set_key_checked(&cbc_key,&key)) != 0)
            printf("\nkey error\n");

        FILE *inpFile;
        FILE *outFile;
        inpFile = fopen("test.txt", "r");
        outFile = fopen("test_results.txt", "wb");

        if(inpFile && outFile) {
        unsigned char ch;

        // A char array that will hold all 8 ch values.
        // each ch value is appended to this.
        unsigned char eight_bits[8];

        // counter for the loop that ensures that only 8 chars are done at a time.
        int count = 0;

        while(!feof(inpFile)) {
                // read in a character
                ch = fgetc(inpFile);

                // print the character
                printf("%c",ch);

                // append the character to eight_bits
                append(eight_bits,1,ch);

                // increment the count so that we only go to 8.
                count++;
                
                const_DES_cblock roundOutput;
                // When count gets to 8
                if(count == 8) {
                    // for formatting
                    printf("%s","\n");

                    // Encrypt the eight characters and store them back in the char array.
                    //DES_encrypt1(eight_bits,&key,ENC);
                    doCBCenc(eight_bits, roundOutput, outFile);

                    // prints out the encrypted string
                    int k;
                    for(k = 0; k < getSize(eight_bits); k++){
                        printf("%c", eight_bits[k]);

                    }

                    // Sets count back to 0 so that we can do another 8 characters.
                    count = 0;

                    // so we just do the first 8. When everything works REMOVE THE BREAK.
                    //break;
                }
            }

        } else {
            printf("Error in opening file\n");
        }

        fclose(inpFile);
        fclose(outFile);

         // End the timing
        gettimeofday(&end, NULL);
 
        // Initialize seconds and micros to hold values for the time output
        long seconds = (end.tv_sec - start.tv_sec);
        long micros = ((seconds * 1000000) + end.tv_usec) - (start.tv_usec);
 
        // Output the time
        printf("The elapsed time is %d seconds and %d microseconds\n", seconds, micros);

    }



Solution

  • Your crypto is at least half correct, but you have a lot of actual or potential other errors.

    As you identified, raw CBC mode can only encrypt data which is a multiple of the block size, for DES 64 bits or 8 bytes (on most modern computers and all where you could use OpenSSL). In some applications this is okay; for example if the data is (always) an MD5 or SHA-256 or SHA-512 hash, or a GUID, or an IPv6 (binary) address, then it is a block multiple. But most applications want to handle at least any length in bytes, so they need to use some scheme to pad on encrypt and unpad on decrypt the last block (all blocks before the last already have the correct size). Many different schemes have been developed for this, so you need to know which to use. I assume this is a school assignment (since no real customer would set such a stupid and wasteful combination of requirements) and this should either have been specified or clearly left as a choice. One padding scheme very common today (although not for single-DES, because that is broken, unsafe, obsolete, and not common) is the one defined by PKCS5 and generalized by PKCS7 and variously called PKCS5, PKCS7, or PKCS5/7 padding, so I used that as an example.

    Other than that:

    The below code fixes the above except that last (which would have been more work for less benefit) plus I removed routines that are now superfluous, and the timing code which is just silly: Unix already has builtin tools to measure and display process time more easily and reliably than writing code. Code I 'removed' is under #if 0 for reference, and code I added under #else or #if 1 except for the cast. The logic for PKCS5/7 padding is under #if MAYBE so it can be either selected or not. Some consider it better style to use sizeof(DES_block) or define a macro instead of the magic 8's, but I didn't bother -- especially since it would have required changes that aren't really necessary.

    // SO70209636
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <openssl/des.h>
    #include <sys/time.h>
    #include <unistd.h>
    
    #define ENC 1
    #define DEC 0
    
    DES_key_schedule key;
    
    #if 0
    int  append(char*s, size_t size, char c) {
        if(strlen(s) + 1 >= size) {
            return 1;
        }
        int len = strlen(s);
        s[len] = c;
        s[len+1] = '\0';
        return 0;
    }
    
    int getSize (char * s) {
        char * t;
        for (t = s; *t != '\0'; t++)
            ;
        return t - s;
    }
    
    void strToHex(const_DES_cblock input, unsigned char *output) {
        int arSize = 8;
        unsigned int byte;
        for(int i=0; i<arSize; i++) {
            if(sscanf(input, "%2x", &byte) != 1) {
                break;
            }
            output[i] = byte;
            input += 2;
        }
    
    }
    #endif
    
    void doBitwiseXor(DES_LONG *xorValue, DES_LONG* data, const_DES_cblock roundOutput) {
        DES_LONG temp[2];
        memcpy(temp, roundOutput, 8*sizeof(unsigned char));
        for(int i=0; i<2; i++) {
            xorValue[i] = temp[i] ^ data[i];
        }
    }
    
    void doCBCenc(DES_LONG *data, const_DES_cblock roundOutput, FILE *outFile) {
    
        DES_LONG in[2];
        doBitwiseXor(in, data, roundOutput);
    
        DES_encrypt1(in,&key,ENC);
    #if 0
        printf("ENCRYPTED\n");
        printvalueOfDES_LONG(in);
        printf("%s","\n");
    #endif
        fwrite(in, 8, 1, outFile);
    
        memcpy(roundOutput, in, 2*sizeof(DES_LONG));
    }
    
    int main(int argc, char** argv)
    {
            const_DES_cblock cbc_key = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef};
            const_DES_cblock IV = {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef};
    #if 0
            // Initialize the timing function
            struct timeval start, end;
            gettimeofday(&start, NULL);
    #endif
            int l;
            if ((l = DES_set_key_checked(&cbc_key,&key)) != 0)
                printf("\nkey error\n");
    #if 1
            DES_cblock roundOutput; // must be outside the loop
            memcpy (roundOutput, IV, 8); // and initialized
    #endif
    
            FILE *inpFile;
            FILE *outFile;
            inpFile = fopen("test.txt", "r");
            outFile = fopen("test.encrypt", "wb");
    
            if(inpFile && outFile) {
            unsigned char ch;
    
            // A char array that will hold all 8 ch values.
            // each ch value is appended to this.
            unsigned char eight_bits[8];
    
            // counter for the loop that ensures that only 8 chars are done at a time.
            int count = 0;
    
    #if 0
            while(!feof(inpFile)) {
                    // read in a character
                    ch = fgetc(inpFile);
    #else
            while( ch = fgetc(inpFile), !feof(inpFile) ){
    #endif
                    // print the character
                    printf("%c",ch);
    #if 0
                    // append the character to eight_bits
                    append(eight_bits,1,ch);
                    // increment the count so that we only go to 8.
                    count++;
    #else
                    eight_bits[count++] = ch;
    #endif
                    
    #if 0
                    const_DES_cblock roundOutput;
    #endif
                    // When count gets to 8
                    if(count == 8) {
                        // for formatting
                        printf("%s","\n");
    
                        // Encrypt the eight characters and store them back in the char array.
                        //DES_encrypt1(eight_bits,&key,ENC);
                        doCBCenc((DES_LONG*)eight_bits, roundOutput, outFile);
    #if 0
                        // prints out the encrypted string
                        int k;
                        for(k = 0; k < getSize(eight_bits); k++){
                            printf("%c", eight_bits[k]);
    
                        }
    #endif
                        // Sets count back to 0 so that we can do another 8 characters.
                        count = 0;
    
                        // so we just do the first 8. When everything works REMOVE THE BREAK.
                        //break;
                    }
                }
    #if MAYBE
                memset (eight_bits+count, 8-count, 8-count); // PKCS5/7 padding
                doCBCenc((DES_LONG*)eight_bits, roundOutput, outFile);
    #endif
            } else {
                printf("Error in opening file\n");
            }
    
            fclose(inpFile);
            fclose(outFile);
    #if 0
             // End the timing
            gettimeofday(&end, NULL);
            // Initialize seconds and micros to hold values for the time output
            long seconds = (end.tv_sec - start.tv_sec);
            long micros = ((seconds * 1000000) + end.tv_usec) - (start.tv_usec);
            // Output the time
            printf("The elapsed time is %d seconds and %d microseconds\n", seconds, micros);
    #endif
    }
    

    PS: personally I wouldn't put the fwrite in doCBCenc; I would only do the encryption and let the caller do whatever I/O is appropriate which might in some cases not be fwrite. But what you have is not wrong for the requirements you apparently have.