Secure, Authenticated Message Transfer
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);
}
}
}