Search code examples
cencryptionaesfwrite

fwrite in C add extra binary characters at the end of file


Requirements:

I'm a developer but with near-zero experience with C until a week ago. For a project, I need to encrypt/decrypt source-code/text files and need to use C for that.

What I did?

I'm using Kokke/tony-AES-c library for it and implementing it with pkcs7 padding as explained in this Gist. The code I wrote is:

main.c

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <stdlib.h>

#define CBC 1
#define DECRYPT 0
#define ENCRYPT 1

#include "aes.h"
#include "pkcs7_padding.h"

const void my_decrypt(char *report, char *output_file, long dlen) {
    FILE *op_file;

    //Initialization Vector
    uint8_t iv[]  = { 0x75, 0x52, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x21, 0x21 };

    uint8_t i;
    char* key = "thisIstheKey";
//    int dlen = strlen(report);
    int klen = strlen(key);

    printf("THE encrypted TEXT STRING = ");
    for (i=0; i<dlen;i++){
        printf("%c", report[i]);
    }
    printf("\n");

    //Proper Length of report
    int dlenu = dlen;
//    if (dlen % 16) {
//        dlenu += 16 - (dlen % 16);
//        printf("The original length of the STRING = %d and the length of the padded STRING = %d\n", dlen, dlenu);
//    }

    //Proper length of key
    int klenu = klen;
    if (klen % 16) {
        klenu += 16 - (klen % 16);
        printf("The original length of the KEY = %d and the length of the padded KEY = %d\n", klen, klenu);
    }

    // Make the uint8_t arrays
    uint8_t hexarray[dlenu];
    uint8_t kexarray[klenu];

    // Initialize them with zeros
    memset( hexarray, 0, dlenu );
    memset( kexarray, 0, klenu );

    // Fill the uint8_t arrays
    for (int i=0;i<dlen;i++) {
        hexarray[i] = (uint8_t)report[i];
    }
    for (int i=0;i<klen;i++) {
        kexarray[i] = (uint8_t)key[i];
    }

    int reportPad = pkcs7_padding_pad_buffer( hexarray, dlen, sizeof(hexarray), 16 );
    int keyPad = pkcs7_padding_pad_buffer( kexarray, klen, sizeof(kexarray), 16 );

    printf("The padded STRING in hex is = ");
    for (i=0; i<dlenu;i++){
        printf("%02x",hexarray[i]);
    }
    printf("\n");

    printf("The padded key in hex is = ");
    for (i=0; i<klenu;i++){
        printf("%02x",kexarray[i]);
    }
    printf("\n");

    // In case you want to check if the padding is valid
    int valid = pkcs7_padding_valid( hexarray, dlen, sizeof(hexarray), 16 );
    int valid2 = pkcs7_padding_valid( kexarray, klen, sizeof(kexarray), 16 );
    printf("Is the pkcs7 padding valid  report = %d  |  key = %d\n", valid, valid2);

    //start the decryption
    struct AES_ctx ctx;
    AES_init_ctx_iv(&ctx, kexarray, iv);

    // decrypt
    AES_CBC_decrypt_buffer(&ctx, hexarray, dlenu);

    // ks
    size_t actualDataLength = pkcs7_padding_data_length( hexarray, dlenu, 16);
    printf("The actual data length (without the padding) = %ld\n", actualDataLength);

    printf("the decrypted STRING in hex = ");
    for (i=0; i<actualDataLength;i++){
        printf("%02x",hexarray[i]);
    }
    printf("\n");

    printf("Printing message from variables1\n");
    char* decrypted_message = printf((char*) hexarray);
    printf("Printing message from variables2\n");
//    printf("Decrypted string (decrypted_message) = %s", decrypted_message);
    printf("Decrypted string (hexarray) = %s", (char*)hexarray);

    // Write file
    printf("Writing contents to %s\n", output_file);
    op_file = fopen(output_file, "wb");
    if (!output_file) {
        /* Unable to open temp file for writing */
        fprintf(stderr, "ERROR: fread error: %s\n", strerror(errno));
    }
    int out_len = sizeof (hexarray);
    fwrite(hexarray, sizeof(unsigned char), out_len, op_file);

    fclose(op_file);
}

const void my_encrypt(char *report, char *output_file, long dlen) {
    FILE *op_file;

    //Initialization Vector
    uint8_t iv[]  = { 0x75, 0x52, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x21, 0x21 };

    uint8_t i;
    char* key = "thisIstheKey";
//    int dlen = strlen(report);
    int klen = strlen(key);

    printf("THE PLAIN TEXT STRING = ");
    for (i=0; i<dlen;i++){
        printf("%c", report[i]);
    }
    printf("\n");

    //Proper Length of report
    int dlenu = dlen;
    if (dlen % 16) {
        dlenu += 16 - (dlen % 16);
        printf("The original length of the STRING = %l and the length of the padded STRING = %l\n", dlen, dlenu);
    }

    //Proper length of key
    int klenu = klen;
    if (klen % 16) {
        klenu += 16 - (klen % 16);
        printf("The original length of the KEY = %d and the length of the padded KEY = %d\n", klen, klenu);
    }

    // Make the uint8_t arrays
    uint8_t hexarray[dlenu];
    uint8_t kexarray[klenu];

    // Initialize them with zeros
    memset( hexarray, 0, dlenu );
    memset( kexarray, 0, klenu );

    // Fill the uint8_t arrays
    for (int i=0;i<dlen;i++) {
        hexarray[i] = (uint8_t)report[i];
    }
    for (int i=0;i<klen;i++) {
        kexarray[i] = (uint8_t)key[i];
    }

    int reportPad = pkcs7_padding_pad_buffer( hexarray, dlen, sizeof(hexarray), 16 );
    int keyPad = pkcs7_padding_pad_buffer( kexarray, klen, sizeof(kexarray), 16 );

    printf("The padded STRING in hex is = ");
    for (i=0; i<dlenu;i++){
        printf("%02x",hexarray[i]);
    }
    printf("\n");

    printf("The padded key in hex is = ");
    for (i=0; i<klenu;i++){
        printf("%02x",kexarray[i]);
    }
    printf("\n");

    // In case you want to check if the padding is valid
    int valid = pkcs7_padding_valid( hexarray, dlen, sizeof(hexarray), 16 );
    int valid2 = pkcs7_padding_valid( kexarray, klen, sizeof(kexarray), 16 );
    printf("Is the pkcs7 padding valid  report = %d  |  key = %d\n", valid, valid2);

    //start the encryption
    struct AES_ctx ctx;
    AES_init_ctx_iv(&ctx, kexarray, iv);

    // encrypt
    AES_CBC_encrypt_buffer(&ctx, hexarray, dlenu);

    printf("the encrypted STRING = ");
    for (i=0; i<dlenu;i++){
        printf("%02x",hexarray[i]);
    }
    printf("\n");

    // Write file
    printf("Writing contents to %s\n", output_file);
    op_file = fopen(output_file, "wb");
    if (!output_file) {
        /* Unable to open temp file for writing */
        fprintf(stderr, "ERROR: fread error: %s\n", strerror(errno));
    }
    int out_len = sizeof (hexarray);
    fwrite(hexarray, sizeof(unsigned char), out_len, op_file);
    fclose(op_file);
}

int main(int argc, char *argv[]) {
    // File pointer
    FILE *input_file, *output_file;

    int operation;

    char * input_file_buffer = 0, output_file_buffer = 0;
    long input_file_length;

    // ////////////////////////////////////////////////////////
    // Read command line arguments
    // ////////////////////////////////////////////////////////

    // Make sure proper command params are supplied
    if (argc != 3) {
        printf("Usage: %s encrypt/decrypt filename\n", argv[0]);
        return -1;
    }

    // Define operation type
    if (strcmp(argv[1], "encrypt") == 0) {
        operation = ENCRYPT;
    } else if (strcmp(argv[1], "decrypt") == 0) {
        operation = DECRYPT;
    }

    // ////////////////////////////////////////////////////////
    // Open File contents
    // ////////////////////////////////////////////////////////

    // Open input_file to encrypt/decrypt.
    input_file = fopen(argv[2], "rb");
    if (!input_file) {
        fprintf(stderr, "ERROR: '%s' file fopen error: %s\n", argv[2], strerror(errno));
        return errno;
    }

    // Read contents of file in buffer
    fseek(input_file, 0, SEEK_END);
    input_file_length = ftell (input_file);
    fseek(input_file, 0, SEEK_SET);
    input_file_buffer = malloc (input_file_length);
    if (input_file_buffer)
    {
        fread(input_file_buffer, 1, input_file_length, input_file);
    }

    // Close input_file
    fclose(input_file);

    // We have contents of file in input_file_buffer, let's print them.
    printf("File contents:\n-------\n%s\n", input_file_buffer);

    if (operation == ENCRYPT) {
        // Let's encrypt input_file_buffer
        my_encrypt(input_file_buffer, argv[2], input_file_length);
    } else if (operation == DECRYPT) {
        my_decrypt(input_file_buffer, argv[2], input_file_length);
    }

    return 0;
}

Makefile

# Compiler
CC       = gcc
CFLAGS   = -Wall

# Library options (Needed for OpenSSL)
LDLIBS   = -lcrypto

# Define input/output files
SOURCES  = aes.c pkcs7_padding.c main.c
OBJFILES = aes.o pkcs7_padding.o main.o
TARGET   = encdec

# Generated files
ENCFILE  = temp_encrypted_file
DECFILE  = temp_decrypted_file

# Start building
all: $(TARGET)

$(TARGET): $(OBJFILES)
    $(CC) $(CFLAGS) -o $(TARGET) $(OBJFILES) $(LDLIBS)

# Start clean up
clean:
    rm -f $(OBJFILES) $(TARGET) $(ENCFILE) $(DECFILE)

Command executed after compile

After compiling (make all), I run commands ./encdec encrypt test.php, which encrypt test.php file.

Original test.php

<?php

// This is comment to check if comment can also be retrieved after decryption
echo "Hello world!";

// One empty lime after the end (Without closing ?> tag) to check if empty line is retrieved after decryption

After running command, one possible output is:

encrypted test.php

cat test.php
��D�zt�5�W�L�QP�8B�n`-`����݇Eֶr��˘�`\��k
��pDZ��8NU��e�T�P|�W�{2�,]��=�'��i:���E��fϓQt�P"���_���,����?�M�����3b3��Al�����|q2������/��
���8�,�˦�VV��q�����a�]�]��#��i5]?����<a�Z����q�I0�?-S�9?%

After decrypting ./encdev decrypt test.php, I get following contents in test.php

decrypted test.php (Checked in vim)

<?php

// This is comment to check if comment can also be retrieved after decryption
echo "Hello world!";

// One empty lime after the end (Without closing ?> tag) to check if empty line is retrieved after decryption










Problem

At the end, I'm getting 10 empty lines, in original file, there was only one.

Could someone please help me understand what mistake am I making and how can I get rid of extra 9 line break in decrypted file?


Solution

  • In my_decrypt() when writing the decrypted data to the file the length determined with pkcs7_padding_data_length() is not used: int out_len = sizeof(hexarray) must be replaced by int out_len = actualDataLength. This makes the decryption work on my machine.

    Furthermore the padding of the ciphertext in my_decrypt() makes no sense. It also has no effect here, since pkcs7_padding_pad_buffer() does not pad due to data_length + pad_byte > buffer_size (and pkcs7_padding_valid() indicates an invalid padding).

    Also, the key should not be padded. If the key does not have the length defined for AES, it is better to display an error message. If a password is to be used instead of the key, a reliable key derivation function like PBKDF2 should be used.

    Finally, for security reasons, don't apply a static IV.