encrypt files using a variant of Vernam Cipher in C language

I'm coding this program that allows you to encrypt files using vernam cipher in C language. I would be glad if you could inspect my code and tell me if I can make some improvements to it or if there are any error.

The request they gave me is:

The goal is to develop a simple application that allows you to encrypt files using a variant of the Vernam cipher, here called bvernan. The proposed algorithm exploits a simple property of the binary operator XOR: (A XOR B) XOR B = A Given a sequence of k-bytes b0 ... bk-1 (called key), the encoding function of a sequence of bytes d0 ... dN, the encoding / decoding function follows the following simple procedure. The sequence d0 ... dN is first divided into N / k blocks (integer division), D0, ..., D [(N / k) -1] each of which consists of exactly k bytes (apart from the 'last sequence which, obviously, may contain a smaller number of bytes). Subsequently each sequence Dj = dj, 0 ... dj, k-1 is transformed into the sequence D'j = d'j, 0 ... d'j, k-1 such that for each i: d'j, i = b (j + i) mod k XOR dj, i That is, the byte in position i of block j is placed in XOR with the byte (j + i) mod k of the key. The output sequence will then be obtained from the juxtaposition of the sequences D'0, ..., D '[(N / k) -1].

This is the code:

file main.c

#include "encode.h"

int main (int argc ,char** argv){
if(argc!=4){
printf("Usage: bvernan keyfile inputfile outputfile \n");
return 1;
}

Key_t* key=openKey(argv[1]);

Register_t* file=openFile(argv[2],argv[3],key->lenght);
encode(file,key);
closeRegister(file);
freeKey(key);
printf("Success!\n");
}



file encode.c

#include "encode.h"

int encode (Register_t* file, Key_t* key ){
encodeDivision(file->buffer,file->bufferLenght,key);
writeF(file);
}
}
void encodeDivision (unsigned char* block,long lenght,Key_t*key){
for(int i=0;i<lenght;i++){
block[i]=block[i]^key->buffer[i];
}
}



file encode.h

#include "key.h"
#include "register.h"

int encode (Register_t* file, Key_t* key );
void encodeDivision (unsigned char* block,long lenght,Key_t*key);



file key.c

#include "key.h"
#include <stdio.h>
#include <stdlib.h>

long keySize (FILE* file){
fseek (file,0,SEEK_END);
long size=ftell(file);
rewind (file);
return size;
}
Key_t* openKey(char* path){
Key_t* kFile= malloc (sizeof(Key_t));
FILE*file= fopen(path,"rb");
kFile->lenght=keySize(file);
kFile->buffer= malloc(kFile->lenght);
fclose(file);
return kFile;
}
void freeKey (Key_t* key ){
free (key->buffer);
free (key);
}



file key.h

typedef struct Key
{
unsigned char* buffer;
long lenght;
} Key_t;

Key_t* openKey(char* path);
void freeKey (Key_t*);



file register.c

#include <stdio.h>
#include <stdlib.h>
#include "register.h"

Register_t* openFile (char* inPath,char* outPath,long bufferLenght){
Register_t* file = malloc (sizeof(Register_t));
file->buffer= malloc(bufferLenght);
file->bufferLenght=bufferLenght;
file->fdIn= fopen(inPath,"rb");
file->fdOut= fopen(outPath,"wb");
if(file->fdOut==NULL || file->fdIn==NULL){
return NULL;
}
return file;
}
}
int writeF (Register_t* file){
int writebyte = fwrite(file->buffer,1,file->bufferLenght,file->fdOut);
return writebyte;
}
void closeRegister (Register_t* file){
free(file->buffer);
fclose(file->fdIn);
fclose(file->fdOut);
free(file);
}



file register.h

#include <stdio.h>
#include <stdlib.h>

typedef struct Register
{
unsigned char *buffer;
FILE *fdIn;
FILE *fdOut;
long bufferLenght;
} Register_t;

Register_t *openFile(char *inPath, char *outPath, long bufferLenght);
int writeF(Register_t *);
void closeRegister(Register_t *);



file Makefile

all: bvernan

bvernan: encode.o key.o main.o register.o
gcc -o bvernan $^ %.o: %.c gcc -c$<

clean:
rm *.o bvernan


if I can make some improvements to it or if there are any error.

Lack of error checking

fopen(), malloc(), ftell(), fread(), (all I/O functions), etc. deserve to have their return values checked for errors.

In particular when a file may fail to open or memory fail to allocate, handle such cases to not forget the other. e. g.: do not forget to fclose(), or fclose() twice when memory allocation failed.

Gracefully handling errors and still maintaining nice code flow is challenging.

some comments would help, especially in .h files. A header files is sometimes all a user sees (or cares to see) .

.h files

Missing code guards.

Reduce namespace scattering. Recommend a common prefix per objects in a .h file

Types

long OK for most files sizes. size_t better for array sizing and indexing.

long lenght ... for(int i=0;i<lenght;i++) --> i and length should be the same type: size_t.

Style

Use an auto formatter to improve code appearance at less time cost.

Tolerant free

Note that free(NULL) is well defined. Do so like-wise for freeKey(NULL);

Spelling

lenght --> length.

Spaces

Style is very dense left=to=right. Some spaces would help.

// block[i]=block[i]^key->buffer[i];
block[i] = block[i] ^ key->buffer[i];


Include order

For xxx.c, I recommend including xxx.h first, to help test its ability to stand alone.

// file register.c

#include "register.h"  // Put first
#include <stdio.h>
#include <stdlib.h>


Unclear why register.h has #include <stdlib.h>. Excessive #include files in a .c file have their place and are not so much a timely concern. (Some coding environments offer a check.)

.h file should use a minimal set.

Naming

Consider using the same case and order for the names key.c, Key_t --> key.c, key_t. keySize(), openKey() --> keySize(), keyOpen().

Allocating

Rather than size by type, size by the referenced object. Easier to code right, review and maintain.

// ptr = malloc(sizeof(hopefully_the_right_type));
ptr = malloc(sizeof *ptr);


Simplistic error handling examples

FILE*file = fopen(path,"rb");
if (file == NULL) {
fprintf(stderr, "Unable to open file <%s> for reading.\n", path);
exit EXIT_FAILURE;
}

Key_t* kFile = malloc(sizeof *kFile);
if (kFile == NULL) {
fprintf(stderr, "Out of memory.\n");
exit EXIT_FAILURE;
}

long size = ftell(file);
if (size == -1 || size > SIZE_MAX) {
fprintf(stderr, "File size problem %ld.\n", size);
exit EXIT_FAILURE;
}


Allow freeKey(NULL)

This allows calls to freeKey(k) without first checking if k is non-null, just like free(NULL) is allowed. Having a check inside freeKey(NULL) allows for simply calls to it, especially if the higher level code itself is in some error recovery block.

void freeKey(Key_t* key) {