Secure, Authenticated Message Transfer

by @im_a_muppet on April 11, 2007

Basically we had to write an app that will encrypt text with a password and write it to a file. It would then have to decrypt it given the same file and password. It is to be used to send the file (binary) over an unsecure channel.

PA4 the assignement

pa4.c my solution

/**
 * main.c
 *
 * @author    Will Mernagh
 * @version PA4
 * @course    COMP 150-ICS
 * @date    Apr 11 2007
 * 
 * @notes    1.I have changed fprintf to print to stdout not stderr so that it matches the given
 *            solution. for a more natural solution change this back.
 *
 *            2. I have one noticable difference between this sol and given sol and that is that    
 *            for a corrupted file (where i deleted a character) i print out a invalid file. given  
 *            sol prints out invalid hash.
 */
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include "global.h"
#include "md5.h"
#include <unistd.h>
#include <crypt.h>

/* Function declerations */
char* getPlaintext();
char * addToBuffer(char *bufferPtr, char *addedStrPtr);
int printBufToFile(char *bufferPtr, char *fileName);
void decode(char *s_out, char *s_in, int bytelen) ;
void encode(char *s_out, char *s_in, int textlen) ;
int bin2byte(char *s);
void byte2bin(char *s, unsigned char b);
int power(int base, int n);
unsigned char* MDString (unsigned char *digestin, char *string);
int printToFile(int textlen, char *digestptr, char *txt_out, char *fileName);
char * readFromFile(int *textlen, char *digestptr, char *txt_in, char *fileName);
char * getKey(int min, int max);

/* definitions for MD5 */
#define MD_CTX MD5_CTX
#define MDInit MD5Init
#define MDUpdate MD5Update
#define MDFinal MD5Final
#define ENCODE (0)
#define DECODE (1)

/**
 * This program encrypts/decrypts plaintext.
 * The user passes -e to encrypt and -d to decrypt
 * The second param must be a file
 * 
 * @param    argc    the number of command line arguments including the program
 * @param    argv    a pointer to an array of strings holding the command line 
 *
 * @return        an int defining its success, failure.
 */
int main (int argc, char * argv[]) {

    char *plainText;
    char *key_in, key[128];
    char *encoded_text, *txt_out, *txt_in;
    int offset, pad, i;
    unsigned char digest[16], *digestptr, digestin[16], *digestinptr;

    digestptr = digest;
    digestinptr = digestin;

    /* check for 2 command-line args */
    if (argc == 3){

        /* if the encrypt option is selected */
        if(strncmp("-e",argv[1],strlen("-e")) == 0 && strlen(argv[1]) == 2){
    
            /* get the text to be encrypted */
            printf("Enter your message now, terminate with a single");
            printf("\n'.' on its own line.\n");
            plainText = getPlaintext();
    
            /* get the key to encrypt text with */
            printf("Please enter the key: ");
            key_in = getKey(4, 8);
            encode(key, key_in, 8);
            setkey(key);
    
    
            if(plainText != NULL){
    
                /* compute the md5 hash */
                printf("Computing MD5 digest...");
                digestptr = MDString(digestptr, plainText);
        
        
                /* check that the md5 hash is at least 16 bytes */
                if(strlen(digestptr) >= 16){
        
                    printf("OK\n");
            
                }else{
        
                    printf("ERROR digest length\n");
             
                    return 1;
            
                }
        
            /* 
               encrypt() takes a 64-byte array as an argument so we need to encode 
               the plaintext into 64-byte arrays. We also need to be sure the last 
               array passed will be of length 64. For this purpose will allocate 
               enough memory such that the encoded_text buffer will be a multiple
               of 64. We do this by adding a pad.
       
               Also each byte in the 64 byte array represents a bit in the messege.
               Therefor we need to disassemble the plaintext into this format
            */
                pad = 64 - ((strlen(plainText)*8)%64);
                encoded_text = (char *) malloc((8*strlen(plainText) + pad)*sizeof(char));
                txt_in = (char *) malloc((pad/8 + strlen(plainText))*sizeof(char));
        
                /* null all chars in string */
                for(i = 0; i < (pad/8 + strlen(plainText)); i++){
            
                    txt_in[i] = '\000';
            
                }
        
                /* cpy plainText into txt_in thus creating a string buffer of correct 
                   size
                */
                strncpy(txt_in, plainText, strlen(plainText));
                free(plainText);
                plainText = txt_in;

                /* Disassemble text */
                encode(encoded_text, plainText, strlen(plainText) + pad/8);
        
        
                /* Here I iteratively encrypt the encoded plaintext 64 bits at a time
                   by passing 64 bytes at a time
                 */
                printf("Encrypting...");            
                offset = 0;        
        
                while(offset < (strlen(plainText)*8 + pad) ){
        
                    encrypt((encoded_text+offset), ENCODE);
                    offset = offset + 64;
            
                }
        
                printf("OK\n");
        
                /* now that the disassembled version of the plaintext is encrypted we convert 
                   it back.
                 */
                txt_out = (char *) malloc((pad/8 +strlen(plainText))*sizeof(char));
                decode(txt_out, encoded_text, (strlen(plainText) + pad/8));
    
                /* This prints the decoded encrypted text to the file. Since the strlen()
                   will not work on the encrypted text I pass the size of the plaintext
                   plus padding 
                */
                printToFile(strlen(plainText) + pad/8, digestptr, txt_out, argv[2]);
        
                /* This frees all the allocated memory */
                free(txt_out);
                free(key_in);
                free(encoded_text);
                free(plainText);

            }
    
            return 0;

        /* if decrypt option is selected */
        }else if(strncmp("-d",argv[1],strlen("-d")) == 0){
            
            int textlen[1], *tl;
            tl = textlen;
            textlen[0] = 0;
    
            /* get the key to decrypt text with */
            printf("Please enter the key: ");
            key_in = getKey(4, 8);
            encode(key, key_in, 8); 
            setkey(key);
    
            /* read text from file - digestptr also gets set to the hash and
               textlen = tl gets set to lengeth of the decoded encrypted text
            */
            txt_in = readFromFile(tl, digestinptr, txt_in, argv[2]);
    
            /* allocate memory for the encoded and plain text */
            encoded_text = (char *) malloc((textlen[0]*8)*sizeof(char));
            plainText = (char *) malloc(textlen[0]*sizeof(char));
    
            /* encode txt to disassembled version */
            encode(encoded_text, txt_in, textlen[0]);
    
            printf("Decrypting...");
            offset = 0; 
    
            /* Protect against bad memory access in encrypt. If result of 'if' statement
               is false then the file is corrupt
             */ 
            if((textlen[0]*8)%64 == 0){
    
                while(offset < textlen[0]*8){
        
                    encrypt((encoded_text+offset), DECODE);
                    offset = offset + 64;
            
                }
        
                printf("OK\n");
    

                /* reassemble */            
                decode(plainText, encoded_text, textlen[0]);

                /* check if the hashes match. the one from the file and the one computed 
                   by the MD5String() based on the decryped txt
                 */
                printf("Validating MD5 digest...");

                if(strncmp(digestinptr, MDString(digestptr, plainText), 16) == 0){
            
                    printf("OK\n");
                    printf("Message follows:\n");
                    printf("%s", plainText);
            
                }else{    
            
                    printf("Hash invalid\n");
                }
            }else{
    
                printf("Invalid file\n");
        
            }

    

            /* This frees all the allocated memory */
            free(encoded_text);            
            free(plainText);
            free(key_in);
            free(txt_in);
            return 0;
    
        }
    }

    printf("Error: Usage: %s [-d/-e FILE]\n", argv[0]);
    return 1;
}

/**
 * Reads from cmd line and if key is invalid length asks for reentry
 * 
 * @param    min        minimum size the key can be
 * @param    max        maximum size the key can be
 *
 * @return            the entered key
 * 
 * @note            returned ptr needs to be freed by user of this function
 */
char * getKey(int min, int max){

    char ch, *buf;
    int index = 0, i;

    /* allocate memory for the key */
    buf = (char *) malloc(sizeof(char)*(max + 1));

    /* pad key with nulls */
    for(i = 0; i <= max; i++){
        buf[i] = '\000';
    }

    /* read until return is entered */
    while ( ( (ch = getchar()) != '\n') && !feof(stdin) && !ferror(stdout) ) {

        /* ensure we do not write past end of buffer */
        if (index < max ) {
            buf[index++] = (unsigned char)ch;
        }else{
            index++;
        }
    } 

    /* this checks that the string entered was indeed between the min and max
       and if not asks for reentry.
     */
    while(strlen(buf) < min || index > max){
        index = 0;
        printf("Please enter a key of 4 to 8 characters\n");

        while ( ( (ch = getchar()) != '\n') && !feof(stdin) && !ferror(stdout) ) {

            /* ensure we do not write past end of buffer */
            if (index < max ) {
                buf[index++] = (unsigned char)ch;
            }else{
                index++;
            }
        } 
    }
    return buf;

}

/**
 * Reads plaintext from cmd line
 *
 * @return            the entered text
 * 
 * @note            returned ptr needs to be freed by user of this function
 */
char * getPlaintext(){

    char *line, *plainText; //TODO malloc
    size_t nbytes = 255;
    ssize_t bytes_read;

    /* initially allocate memory here for the line read */
    line = (char *) malloc(255*(sizeof(char)));
    plainText = NULL;

    /* run until a '.' is read */
    while (1)
    {
        /* getline will reallocate memory if line is too small (I love getline!)
           but we need to free line later
         */
        bytes_read = getline(&line, &nbytes, stdin);

        if (bytes_read > 0)
        {
    
            /* if end of input denoted by '.' */
            if(strlen(line) == 2 && strspn(line, ".")){
        
                if(plainText == NULL){
        
                    return plainText;
            
                }
        
                /* end of plain text */
                /* need to free allocated memory */
                free(line);
                return plainText;
    
            }else{
    
                /* add line to buffer */
                plainText = addToBuffer(plainText, line);
        
            }
        }
    }

}

/**
 * Appends a line to a string buffer
 *
 * @bufferPtr        pointer to buffer to append to
 * @addedStrPtr        pointer to string to append with
 *
 * @return            the new buffer
 * 
 * @note            returned ptr needs to be freed by user of this function
 */
char * addToBuffer(char *bufferPtr, char *addedStrPtr){

    char *retString;

    /* allocate memory to size of buffer plus line */
    if(bufferPtr != NULL && addedStrPtr != NULL){

        retString = (char *)calloc(strlen(bufferPtr) + strlen(addedStrPtr) + 1, sizeof(char));

    }else if (addedStrPtr != NULL){

        retString = (char *)calloc(strlen(addedStrPtr) + 1, sizeof(char));

    }


    /* if alloc worked */
    if(retString != NULL){

        /* copy old buffer into new buffer */
        if(bufferPtr != NULL){
    
            strncat(retString, bufferPtr, strlen(bufferPtr));
            free(bufferPtr);
    
        }

        /* append if there is somthing to append */
        if(addedStrPtr != NULL){

            strncat(retString, addedStrPtr, strlen(addedStrPtr));

        }

    }else{

        fprintf(stdout, "Error: Memory allocation\n");
        exit(1);

    }    

    return retString;

}


/**
 * Prints the size, hash (digest), encrypted text to file
 *
 * @textlen            length of the encrypted text
 * @digestptr        pointer to the hash of the text
 * @txt_out            the encrypted text
 * @fileName        the file to write to
 *
 * @return            0 is success
 * 
 */
int printToFile(int textlen, char *digestptr, char *txt_out, char *fileName){

    FILE *output_file;
    int mesglenbuf[1];

    /* calculate the number of bytes that will be written to the file */
    mesglenbuf[0] = (textlen + 16 + 4);

    /* open the output file */
    output_file = fopen(fileName, "wb");

    if(!output_file)  {

        fprintf(stdout, "Error: unable to open output file %s for writing\n", fileName);
        exit(1);

    }

    fwrite(mesglenbuf, sizeof(mesglenbuf[0]), 1, output_file);
    fwrite(digestptr, sizeof(unsigned char), 16, output_file);
    fwrite(txt_out, sizeof(char),textlen, output_file);

    fclose(output_file);

    return 0;
}

/**
 * Reads the size, hash (digest), encrypted text from a file
 *
 * @textlen            length of the encrypted text
 * @digestptr        pointer to the hash of the text
 * @txt_in            the encrypted text
 * @fileName        the file to write to
 *
 * @return            the encrypted text read from the file
 *
 * @note            returned ptr needs to be freed by user of this function
 * 
 */
char * readFromFile(int *textlen, char *digestptr, char *txt_in, char *fileName){

    FILE *input_file;
    int mesglenbuf[1];

    input_file = fopen(fileName, "rb");

    if(!input_file)     {

        fprintf(stdout, "Error: unable to open output file %s for reading\n", fileName);
        exit(1);

        return NULL;

    }

    fread(mesglenbuf, sizeof(mesglenbuf[0]), 1, input_file);
    fread(digestptr, sizeof(unsigned char), 16, input_file);

    txt_in = malloc((mesglenbuf[0] - 20) );
    fread(txt_in, sizeof(char), (mesglenbuf[0] - 20), input_file);
    textlen[0] = mesglenbuf[0] - 20;

    fclose(input_file);

    return txt_in;
}

/**
 * Computes MD5 hash from string in
 *
 * @digestin    pointer to hash digest computed
 * @string        pointer to the string to hash
 *
 * @return        the hash digest pointer
 *
 */
unsigned char* MDString (unsigned char *digestin, char *string){

    MD_CTX context;
    //unsigned char digest[16];
    unsigned int len = strlen (string);

    MDInit (&context);
    MDUpdate (&context, string, len);
    MDFinal (digestin, &context);

    return digestin;

}


/**
 * Calculates the base to the power of n
 *
 * @base    The base to multiply
 * @n        The number of times to multiply
 *
 * @return    The result
 * 
 */
int power(int base, int n){

    int p;

    for (p = 1; n > 0; --n){
        p = p * base;
    }

    return p;

}

/**
 * converts a byte to a binary string. e.g. converts 'F' to "01000110"
 *
 * @s    the resulting binary repersentation
 * @b    the byte to be convertef
 *
 */
void byte2bin(char *s, unsigned char b){

    int i;

    /* mask = 10000000 binary */
    unsigned char mask = 0x80;

    for (i = 0; i < 8; ++i){

        /* if bitewise AND of b with 10000000 > 0 then msb is '\1'
         * if bitewise AND of b with 010000000 > 0 then msb - 1 is '\1'
         * etc.
         * we compare b & 10000000, 01000000, 00100000 etc. (achieved by >>) 
         */
        if (b & mask){

            *(s + i) = '\1';

        }else{

            *(s + i) = '\0';

        }

        mask >>= 1;
    }
}

/**
 * converts a binary string to a byte. e.g. converts "01000110" to 'F'
 *
 * @s    binary string to be converted to byte
 *
 * @return    the resulting byte
 */
int bin2byte(char *s){

    int i, j, ret = 0;

    for (i = 0; i < 8; i++) {

        /* for every bit set to 1 we add 2^j because that is the value of the bit */
        if (*(s + i) == '\1') {

            j = 7 - i;
            ret += power(2, j);
    
        }
    }

    return(ret);
}

/**
 * converts a string to a binary repersentation of the string in 8 byte blocks. 
 * for every 8 bytes (8 characters) it will create a 64 bit binary repersentation
 * of the bytes.
 *
 * @s_out    the binary string repersentation of the character bytes
 * @s_in    the character string to be transformed
 *
 * @note    s_in should be a multiple of 64 bits i.e. textlen mod 64 = 0
 */
void encode(char *s_out, char *s_in, int textlen) {

    int h, i, j = 0;

    for (h = 0; h < textlen; h = h+8){

        j=0;

        for (i = 0; i < 8; i++) {

            j = ((i + h) * 8);
    
            if((j >= textlen*8) || ((i+h) >= textlen)){
                return;
            }
        
            byte2bin(s_out + j, *(s_in + (i + h)));
    
        }
    }

}

/**
 * converts a binary string repersentation to a byte array (character string).
 * for every 8 bits (1 characters) it will create a 1 byte character repersentation
 * of the string.
 *
 * @s_out    character bytes transformed from the binary string
 * @s_in    the binary string repersentation of character bytes
 *
 * @note    s_in should be a multiple of 64 i.e. bytelen mod 64 = 0
 */
void decode(char *s_out, char *s_in, int bytelen) {

    int h, i, j = 0;

    for(h = 0; h < bytelen; h = h+8){

        j=0;

        for (i = 0; i < 8; i++) {

            j = ((i+h) * 8);
    
            if((j >= bytelen*8) || ((i+h) >= bytelen)){
                return;
            }
    
            *(s_out + (i+h)) = bin2byte(s_in + j);
    
    
        }

    }

}
blog comments powered by Disqus
Tweet