Click here to Skip to main content
11,519,139 members (68,732 online)
Click here to Skip to main content

File Encryption/Decryption Tutorial in C++

, 23 Feb 2015 CPOL 27K 1.5K 52
Rate this:
Please Sign up or sign in to vote.
Tutorial on how to encrypt/decrypt files using C++

Introduction

The goal of this article is to demostrate how one can perform encryption/decryption of data - and files in particular in C++. Throughout the article, we will touch on aspects such as encryption algorithm selection, reading files, choosing a padding scheme, and modes of operation.

Background

Encryption is the process of encoding data in such a way that only intended recipients can read the original data (also called the plaintext). The encoded data is called ciphertext. The process of retrieving plaintext from ciphertext is called decryption.

Encryption and decryption usually happen using a cryptographic key. Encryption/decryption schemes are generally divided into two classes: symmetric and assymetric algorithms. In symmetric algorithms, only a single key is used both for encryption and decryption. Whereas in assymetric algorithms, a public key is usually used for encryption and private key for decryption.

In this article, we will encrypt the files using a symmetric block cipher called Blowfish. In particular, we will borrow George Anescu's implementation of the Blowfish algorithm.

The fact that the algorithm is a block cipher as we mentioned means the following. The message which we want to encrypt is broken into blocks and each block is encrypted. In case of Blowfish, the length of the block is 8 bytes. Which means the message must be broken down to blocks with length of 8 bytes.

Reading Files

Like we said, this tutorial is about how to encrypt/decrypt files. Hence, we must read the file contents before processing them. Briefly, two options come to my mind when thinking about how to read files.

  1. To read the full file contents to memory - and encrypt/decrypt that chunk of data, or
  2. to read the file in smaller chunks (say 512 bytes), and process each chunk right away.

Clearly the issue with the first approach is that if the file is too large, it may become problematic to read that large file into memory, while the second approach does not suffer from this issue. Nevertheless, in this article, we implement the first method, and think it should not be a problem for the reader to change the project such that approach number 2 is used.

Padding Method

Like we mentioned, one of the important aspects when talking about encrypting/decrypting data is the padding scheme. As we noted earlier, since Blowfish has a block size of 8 bytes, the length of data that must be encrypted/decrypted must be multiple of 8 bytes.

So if some file we want to encrypt does not have size multiple of 8 bytes, we must pad it with additional bytes such that the length becomes multiples of 8. After decryption, we must remove those additional bytes.

For example, if the file we are willing to encrypt has a length of 12 bytes, we must pad it so that its length becomes 16 bytes. Now, we are left with plain text which has 16 bytes length. When we encrypt it, the ciphertext will also be 16 bytes in length. Now, if the user decrypts the ciphertext - she must be able to tell how many bytes are redundant. In other words, when reconstructing the plain text, she must be able to tell that only the first 12 bytes in our example represented the real data. The padding scheme called PKCS#5 (described here) that we will use can solve this issue.

PKCS#5 padding scheme works in the following way. The message say M is concatenated with padding string PS in the following way. PS consists of 8-(N mod 8) bytes where each byte has value 8-(N mod 8). Where N is the length of original message M. For example, if the original message is 14 bytes long, since block size of Blowfish is 8 bytes, the padding string will look like: PS = 02 02. If the original message is 12 bytes long on the other hand, the padding string will look like: PS = 04 04 04 04. Finally, if the padding string is already multiple of 8 bytes, the padding string will be: PS = 08 08 08 08 08 08 08 08.

Actually, I think it is not very hard to come up with a padding scheme yourself (although it is probably better to use an estabished scheme). For example, one padding scheme I came up with when thinking about this issue was the following. Regardless of whether the original plaintext length is multiple of 8, always append a dummy block, last byte of which says how many bytes were needed for padding. For example, if the original message is 12 bytes long, the padded string may look like:

Final Message = OriginalMessage || 00 00 00 00 || 00 00 00 00 00 00 00 04.

You can see 4 bytes with value 00 were appended to the message since the message length was 12 (16-12=4), and the last dummy block, last byte of which contains the number (4) of bytes that was added. Now, after the decryption, the user could read the last byte, discard the last block, and also discard as many bytes from the remaining message, as were mentioned in the last byte of padded block.

Nevertheless, in this article, we will use the PKCS#5 padding scheme. In this scheme, in order to reconstruct the plain text, the user will read the last byte, and discard as many bytes as are specified by that byte. Some additional checks can also be performed as it can be seen in code.

Mode of Operation

Finally, few words about the mode of operation. As we noted the length of the plaintext/ciphertext which must be passed to the encryption/decryption method must be multiple of 8 bytes - due to the block length of Blowfish cipher which is 8 bytes. Now, a mode of operation describes how to apply the cipher to data which consists of multiple 8 byte blocks. For example, the ECB (Electronic Codebook) mode, processes each block in parallel. Below on image is shown how ECB mode looks like. Each "plaintext" block in the image refers to a single 8 byte block of the plain text. You can see how they are processed in parallel. Same principle applies during decryption.

There are more secure versions than ECB, for example Cipher Block Chaining (CBC) mode. Interested reader is referred here to read more details about modes of operations. It should be noted that in this article we use ECB mode for encryption and decryption. If user wishes to use more secure mode of operation for example CBC, we refer the reader to George Anescu's implementation of the Blowfish cipher (the one we used) to see how that can be realized: link.

Code

Armed with the above knowledge, it should not be difficult to understand/use the code (which is also well documented). In the code, we just read a file, encrypt it - and then immediately decrypt the encrypted data, and store it elsewhere. Clearly padding is also used. For full code, please see the project. Readers may notice this is in a certain way mix of C and C++ code, which should not be a big issue.

    const char *filePath = "c:\\image001.jpg";

    // Get file size which must be encrypted
    long originalSize = GetFileSize(filePath);
   
    // Make this size multiple of block size (8 bytes).
    long multipleSize = (originalSize / 8 + 1) * 8;
    
    // Store number of bytes that were needed for padding.
    int nrOfPaddingBytes = (int)(multipleSize - originalSize);
    
    // Now, read the file.
    unsigned char * fileContents = ReadFile(filePath);
    
    // Create a large buffer to store file contents (including padding bytes),
    // and copy file contents to it.
    unsigned char * buffer = (unsigned char*) malloc(multipleSize);
    memcpy(buffer, fileContents, originalSize);
    
    // Allocate space for ciphertext also (should also have size multiple of 8).
    unsigned char * ciphertext = (unsigned char*) malloc(multipleSize);
  
    // Delete old file buffer.
    free(fileContents);
    fileContents = NULL;
    
    // Pad the plaintext
    for(int i = 0; i < nrOfPaddingBytes; i++)
    {
        buffer[originalSize+i] = nrOfPaddingBytes;
    }
      
    try
    {
        // Create Blowfish object used for encryption/decryption
        // -we just use some default key.
        CBlowFish oBlowFish((unsigned char*)"defkey00", 8);
        
        // Do encryption
		// Defaut mode of operation is ECB.
        oBlowFish.Encrypt(buffer, ciphertext, multipleSize);
        
        memset(buffer,0,multipleSize);

		printf("Encryption done. \nNow performing decryption.\n");
        
        // Do decryption
        oBlowFish.Decrypt(ciphertext, buffer, multipleSize);
        
		// Perform check on padding pattern.
        // At least last byte should be padding byte. Check its range
        if(buffer[multipleSize - 1] < 1 || buffer[multipleSize - 1] > 8)
		{
		    free(buffer);
            free(ciphertext);
            exit(1);
		}
        
		// Also check on padding pattern:
        // last nr. of padding bytes should be equal to the padding byte.
        bool t = true;
        for(int i = 0; i < buffer[multipleSize - 1] - 1; i++)
        {
            if(buffer[multipleSize - 1] != buffer[originalSize + i])
            {
                t = false;
                break;
            }
        }
        
        // Wrong padding pattern, we exit.
        if(t == false)
		{
			free(buffer);
            free(ciphertext);
            exit(1);
		}
        
        // Create new destination file.
        FILE* fileDest = fopen("c:\\image001res.jpg", "wb");
        if(fileDest == NULL)
        {
			free(buffer);
            free(ciphertext);
            printf("Error creating destination file /n");
            exit(1);
        }
        
        
        // Write the plaintext to file without padding bytes.
        if(fwrite(buffer, 1, multipleSize-buffer[multipleSize-1], fileDest) 
           != multipleSize-buffer[multipleSize-1])
		{
			free(buffer);
            free(ciphertext);
            exit(1);
		}
        
        printf("Result written to: c:\\image001res.jpg");

        // Close destination file.
        fclose(fileDest);
        
        // Free plain text buffer
        free(buffer);
        buffer = NULL;
        
        // Free cipher text buffer
        free(ciphertext);
        ciphertext = NULL;   
    }
    catch(...)
    {
        printf("Exception occured /n");
        if(buffer)
            free(buffer);
        if(ciphertext)
            free(ciphertext);
        
        exit(1);
    }
    

History

  • Version 1.00 - Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

giorgi.m

Georgia Georgia
No Biography provided

Comments and Discussions

 
QuestionDo it a different way Pin
feanorgem26-Feb-15 16:35
memberfeanorgem26-Feb-15 16:35 
AnswerRe: Do it a different way [modified] Pin
giorgi.m26-Feb-15 20:29
membergiorgi.m26-Feb-15 20:29 
GeneralRe: Do it a different way Pin
feanorgem27-Feb-15 1:38
memberfeanorgem27-Feb-15 1:38 
BugSome notes Pin
VaKa16-Feb-15 4:14
memberVaKa16-Feb-15 4:14 
GeneralRe: Some notes Pin
giorgi.m16-Feb-15 8:03
membergiorgi.m16-Feb-15 8:03 
Questiongood article Pin
mdeaconu11-Feb-15 21:58
membermdeaconu11-Feb-15 21:58 
Questiontypo Pin
mike136921-Oct-14 2:24
membermike136921-Oct-14 2:24 
AnswerRe: typo Pin
geo dev21-Oct-14 2:50
membergeo dev21-Oct-14 2:50 
GeneralRe: typo Pin
mike136921-Oct-14 3:10
membermike136921-Oct-14 3:10 
GeneralRe: typo Pin
qmickecs22-Oct-14 3:57
memberqmickecs22-Oct-14 3:57 
GeneralMy vote of 4 Pin
Ciprian Beldi20-Oct-14 22:25
memberCiprian Beldi20-Oct-14 22:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150604.1 | Last Updated 23 Feb 2015
Article Copyright 2014 by giorgi.m
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid