C getopt not working properly (and probably other things later)

sweenish

Diamond Member
May 21, 2013
3,656
60
91
First the code:
Code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "tea.h"
#include "des.h"

/****************************************************************************************
 * The program must be called with two options set and a filename given.
 * Example: ./blockCode -eo foo.txt
 *    This calls the program to (e)ncrypt using the (o)utput feedback mode a file foo.txt
 * Options:
 *   -e         : Encrypt the file; both TEA and DES are used, so two output files are
 *                produced.
 *   -d         : Decrypt a file; user will be prompted for the decryption algorithm
 *   -c         : Uses the Counter method to encrypt/decrypt
 *   -o         : Uses the Output Feedback method to encrypt/decrypt
 * When calling the file it is required that one and only one of -e and -d is called and
 * one and only one of -c and -o is called.
 ****************************************************************************************/
int main(int argc, char* argv[])
{
    extern char* optarg;    // These are needed to 
    extern int optind;      // process the options
    int option, eFlag = 0, dFlag = 0, cFlag = 0, oFlag = 0;  // Flags explained above
    char filename[21];      // Holds the filename
    char* f = filename;
    int method;             // Helps call the proper function for decryption
    clock_t t;              // This will aid in timing the executions
    double time;            // Variable used to display function execution time

    if (argc < 2) {
        printf("USAGE: ./blockCode -<e/d><c/o> <file name>\nSee README\n");
        return -1;
    }

    while ((option = getopt(argc, argv, "edco") != -1)) {
        printf("Processing option %d\n", (char)option);
        switch (option) {
            case 'c':
                cFlag = 1;
                break;
            case 'd':
                dFlag = 1;
                break;
            case 'e':
                printf("Processing e option\n");
                eFlag = 1;
                break;
            case 'o':
                oFlag = 1;
                break;
            case '?':
                printf("Improper option used\nConsult README\n");
                return -1;
        }
    }

    if (eFlag == 0 && dFlag == 0) {
        printf("Did not choose to encyrpt or decrypt\nSee README\n");
        return -1;
    }
    else if (eFlag == 1 && dFlag == 1) {
        printf("Cannot encrypt and decrypt at same time\nSee README\n");
        return -1;
    }
    else if (oFlag == 0 && cFlag == 0) {
        printf("Did not choose CTR or OFB method\nSee README\n");
        return -1;
    }
    else if (oFlag == 1 && cFlag == 1) {
        printf("Cannot use CTR and OFB methods at same time\nSee README\n");
        return -1;
    }
    
    if (optind == argc) {
        printf("No file name given\nSee README\n");
        return -1;
    }
    else {
        f = argv[optind];
        printf("Filename: %s", filename);
    }
    
    if (dFlag == 1) {
        printf("Choose decryption method:\n   1. TEA\n   2. DES\nEnter the NUMBER of your choice: ");
        scanf("%d", &method);
        if (method < 1 || method > 2) {
            printf("Only choose 1 or 2.\nExiting...\n");
            return -1;
        }
    }

    if (eFlag == 1) {
        if (cFlag == 1) {
            t = clock();
            tea_ctr_encrypt(filename);
            t = clock() - t;
            time = ((double) t)/CLOCKS_PER_SEC;
            printf("TEA CTR took %f seconds\n", time);

            t = clock();
            des_ctr(filename);
            t = clock() - t;
            time = ((double) t)/CLOCKS_PER_SEC;
            printf("DES CTR took %f seconds\n", time);

        }
        else {
            t = clock();
            tea_ofb_encrypt(filename);
            t = clock() - t;
            time = ((double) t)/CLOCKS_PER_SEC;
            printf("TEA OFB took %f seconds\n", time);

            t = clock();
            des_ofb(filename);
            t = clock() - t;
            time = ((double) t)/CLOCKS_PER_SEC;
            printf("DES OFB took %f seconds\n", time);
        }
    }
    else if (method == 1) {
        if (cFlag == 1) {
            t = clock();
            tea_ctr_decrypt(filename);
            t = clock() - t;
            time = ((double) t)/CLOCKS_PER_SEC;
            printf("TEA CTR took %f seconds\n", time);
        }
        else {
            t = clock();
            tea_ofb_encrypt(filename);
            t = clock() - t;
            time = ((double) t)/CLOCKS_PER_SEC;
            printf("TEA OFB took %f seconds\n", time);
        }
    }
    else {
        if (cFlag == 1) {
            t = clock();
            des_ctr(filename);
            t = clock() - t;
            time = ((double) t)/CLOCKS_PER_SEC;
            printf("DES CTR took %f seconds\n", time);
        }
        else {
            t = clock();
            des_ofb(filename);
            t = clock() - t;
            time = ((double) t)/CLOCKS_PER_SEC;
            printf("DES OFB took %f seconds\n", time);
        }
    }

    return 0;
}
This is the entire main funtion, I have not included the other files required to compile.
Here is a Dropbox link to all the code: https://www.dropbox.com/sh/x65c9aiypfhmmio/AADpPFcwXWMhGsy6GbPWpov6a?dl=0

The gist is that this is an assignment where I am supposed to be be able encrypt and decrypt a text file using TEA and DES algorithms. I am also required to use two different block cipher methods. I chose Counter and Offset Feedback.

Instead of a convoluted text menu, I decided to try and get fancy by allowing options to be used.

(e)ncrypt would create two ciphertext files, one for each algorithm.
(d)ecrypt prompts the user for the encryption algorithm, and then decrypts the file.
(c)ounter specifies the counter block cipher method.
(o)ffset feedback is the other block cipher method

I've got it down to compiling with just one warning about assigning the filename to a variable, but I'm worried less about that.

Right now, my getopt code doesn't appear to work, and I can't figure it out. It looks just like so many of the examples I've used as reference.
I make this call: ./blockCode -ec Sample.txt
The print statements just tell me that it's processing option 1 for both options, and then I get an error message about not choosing to encrypt or decrypt.

I need to get over this hurdle before I can move on to actually knowing if everything else is working.

Any help as to what stupid thing I'm missing will be greatly appreciated.
 

Exophase

Diamond Member
Apr 19, 2012
4,439
9
81
This line is probably not doing what you think it is:

Code:
while ((option = getopt(argc, argv, "edco") != -1))

The != operator has a higher precedence than the = operator. So this part:

Code:
getopt(argc, argv, "edco") != -1

Is evaluated first. And it evaluates to 1 whenever the return isn't -1. So you get 1 the first two times and 0 the third time.

Try changing it to this:

Code:
while (((option = getopt(argc, argv, "edco")) != -1))

C's precedence rules can be confusing so it's generally good practice to err on the side of more parentheses.
 

sweenish

Diamond Member
May 21, 2013
3,656
60
91
Like I said, I knew it would be something stupid.

Thank you very much.
 

sweenish

Diamond Member
May 21, 2013
3,656
60
91
And, as it turns out, my filename is all corrupt. Probably shouldn't be a surprise since I said I was getting a warning on compile.

The relevant code is still in the main function. The filename is the remaining argument when the program is called. It doesn't like being saved to an array of characters, since the argument is a pointer to an array of characters (char** argv). I tried de-referencing it once, but it still didn't like that.

I'm pretty sure it has to do with mixing my char* foo and my char foo[] types. I would like to not have to size for the filename every time I pass it around, though.
 

Exophase

Diamond Member
Apr 19, 2012
4,439
9
81
You never copy anything to filename. You make f point to filename, then you make it point to argv[optind]. You could just use f instead of filename.
 

sweenish

Diamond Member
May 21, 2013
3,656
60
91
Yeah, I had tried to use filename = argv[optind], but that also throws a warning when I compile. I tried the pointer to get the types to match. I had also tried *f = argv[optind], but that was a mismatch. When I get flustered, I just start throwing stuff against the wall, so to speak.

I'll give just using f a go.

EDIT: It's compiling clean and passing the filename just fine now. My current issue is trying to actually get it to encode and decode. CTR method is only changing the first letter of each 8 byte block, and OFB is seg-faulting in TEA.

EDIT: Got it to not segfault in TEA OFB by actually understanding TEA a bit better. Still only changing the the first character in each block.
Sample round:
Code:
block: h I foun
Crypto:  I foun

EDIT: Kind of fixed TEA OFB. It encrypts whole blocks now, but it won't decrypt properly. Since the only difference is whether I call TEA encipher or decipher, the error is in my block cipher code somewhere. DES isn't working either, so that needs work. Going to mess with the CTR functions to get them going a little better.
 
Last edited:

Exophase

Diamond Member
Apr 19, 2012
4,439
9
81
Yeah, I had tried to use filename = argv[optind], but that also throws a warning when I compile. I tried the pointer to get the types to match. I had also tried *f = argv[optind], but that was a mismatch. When I get flustered, I just start throwing stuff against the wall, so to speak.

I'll give just using f a go.

It sounds like there are gaps in your understanding of how pointers and arrays in C work. To be able to program effectively in C you really need to understand why those two things resulted in the errors they did and why they didn't make sense to try. I don't know if your other problems are related to similar misunderstandings but it'd best to clear this up before looking further into it.

This page offers a decent explanation, probably better than I can really give right now:

https://www.le.ac.uk/users/rjm1/cotter/page_59.htm

But I can answer questions if this isn't clear to you.
 

sweenish

Diamond Member
May 21, 2013
3,656
60
91
I have primarly learned C++, and some of the extra tools it provides, namely <string> make this so much easier.

The only reason I'm doing this in C is the assignment requirement. I do have that particular problem fixed, thanks to your help. And you're right. I do have a gap in understanding as far as this stuff works in C. I will definitely take the time to read the information you provided in that link later this week.

As my next update, I have both block cipher methods under the TEA algorithm working just fine. On deciphering, there is some garbage at the end of the file from the variables self-padding, but I'm not going to sweat that.

Now I just have to fix my DES implementations.
 

sweenish

Diamond Member
May 21, 2013
3,656
60
91
Bump because I need more knowledge.

My DES implementation is still not working, and again, I think it's something basic. A friend of mine that I won't be able to see until later this evening believes that it has something to do with the fact that I'm reading and writing to file, probably mostly writing. He said he was able to encrypt and decrypt a string just fine without writing to file. When I write to file, gedit gives me an error.

The Dropbox link in the first post contains my code and some sample files (64chars, etc.), and it's changed a little since I initially posted. I got rid of most of my options and just automatically encrypt and decrypt. I will include my DES Output Feedback function here, though.

Parameters are strings, first one is the file to be read, the second is the file to be written.

Code:
void desofb(char* in, char* out)
{
    DES_cblock iv = {0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xee};
    DES_cblock key;
    DES_key_schedule keySchedule;
    unsigned int lengthOfFile;
    int result;
    size_t check;
    char* keySeed = "d562g735";         // Static seed value for key generation
    char* messageIn;
    char* messageOut;

    // Open input file and get its length
    FILE *fp_in = fopen(in, "r");
    if (fp_in == NULL) {
        printf("Error opening read-from file for DES OFB\n");
        return;
    }
    fseek(fp_in, 0, SEEK_END);
    lengthOfFile = ftell(fp_in);
    rewind(fp_in);

    // Allocate memory for the input and output
    messageIn = (char *) malloc(lengthOfFile);
    messageOut = (char *) malloc(lengthOfFile); 

    // Read the file into memory
    check = fread(messageIn, 1, lengthOfFile - 1, fp_in);
    if (check != (lengthOfFile - 1)) {
        printf("Issue reading file into array\n");
        return;
    }
    
    // Generate and check DES key
    DES_string_to_key(keySeed, &key);
    if ((result = DES_set_key_checked(&key, &keySchedule)) != 0) {
        if (result == -1) {
            printf("ERROR: key parity is incorrect\n");
        } else {
            printf("ERROR: weak or semi-weak key\n");
        }
        return;
    }
    
    // Actual encryption
    DES_ofb_encrypt((unsigned char*)messageIn, (unsigned char*)messageOut, 8, lengthOfFile - 1, &keySchedule, &iv);
    
    // Open output file and write output
    FILE* fp_out = fopen(out, "w");
    if (fp_out == NULL) {
        printf("Error opening write-to file for DES OFB\n");
        return;
    }
    fwrite(out, 1, lengthOfFile - 1, fp_out);

    // Close streams and free memory
    fclose(fp_in);
    fclose(fp_out);
    free(messageIn);
    free(messageOut);
}

My counter method is more inherently broke in DES, but once this writing to file thing is sorted out, I will likely just change it to something else that DES has a native implementation of, to make my life easier.
 

Ken g6

Programming Moderator, Elite Member
Moderator
Dec 11, 1999
16,633
4,562
75
I see a few things.

1. You're not checking that malloc succeeded. Sure, these days, it should always have enough space; but you should check just in case.

2. If I read this right, you're encrypting in blocks of 8 bytes? If the input file is 1 byte long, should the output file be 1 byte or 8?
 

TheRyuu

Diamond Member
Dec 3, 2005
5,479
14
81
My counter method is more inherently broke in DES, but once this writing to file thing is sorted out, I will likely just change it to something else that DES has a native implementation of, to make my life easier.

I don't see how the choice of block cipher matters when it comes to CTR mode (or any mode for that matter).
 

Exophase

Diamond Member
Apr 19, 2012
4,439
9
81
Code:
check = fread(messageIn, 1, lengthOfFile - 1, fp_in);
Code:
fwrite(out, 1, lengthOfFile - 1, fp_out);

Those subtractions by 1 should not be in there. You're reading and writing one less byte than you should be.
 

sweenish

Diamond Member
May 21, 2013
3,656
60
91
I don't see how the choice of block cipher matters when it comes to CTR mode (or any mode for that matter).
openssl has functions for specific block cipher modes like OFB and CBC, but not CTR, which just means you would use ECB while implementing CTR around it, as opposed to just calling a single function. I said what I did because I was having major issues with DES in general, so if I got OFB working, it would be trivial to just use a different DES function call as opposed to working with a DES function call AND creating my block cipher around it.

Code:
check = fread(messageIn, 1, lengthOfFile - 1, fp_in);
Code:
fwrite(out, 1, lengthOfFile - 1, fp_out);
Those subtractions by 1 should not be in there. You're reading and writing one less byte than you should be.
Caught on to that, they were there thinking to avoid reading a character that might have been messing stuff up at the end of the file.

My issue ended up being something related to malloc, and my inability to use it properly. Sprinkle a couple refactor errors throughout, and that's the state my code was in when I last asked. So instead of char* and malloc, I switched to char foo[x] that I initialized with memset, and things started working.

As for CTR, I was pleased that my CTR increment implementation largely worked, but only the first character was getting changed in each block. This was because XOR wanted ints, not pointers, which makes sense. In TEA all I had to do was de-reference my blocks, but they were already of type long. De-referencing an array of characters with nothing but *foo was only giving me one character, so I loop through the array now. So I added another loop to individually XOR each character from my plaintext and my ECB output, and that did the trick.

The code is finished and should compile fine.

I lessened my original scope significantly since at the end of the day, I just needed to make timing comparisons, not have an actual tool for encryption and decryption. If anyone still wants to view it, please use a text editor with tabs set to 4 spaces. I worked on it on a variety of machines with a variety of text editors. Some used an actual TAB, some just jump four spaces.

I have no clue why 8 spaces is a Linux default tab size. It's way too big.