[OpenSSL] Decrypting with the OpenSSL API

Before you can decrypt something, you have to generate it! You can generate encrypted data with OpenSSL on the commandline using the enc command. The encryption process requires a key and iv (initialisation vector) pair, which can be derived from a given passphrase. The enc command gives you the option of specifying, either the passphrase or the key and iv pair. For this short tutorial, let’s use specify the key and iv pair on the command line. To use perform the encryption, use the following command:

openssl enc -e -bf-cbc -in test.txt -out test.bin \
     -K 12345678911234567892123456789312 \
     -iv 00000000

The above command instructs OpenSSL to perform encryption (“-e”) on the file test.txt (“-in test.txt”), using the blowfish with cipher block chaining (“-bf-cbc”). The encrypted data would be placed in the file “test.bin” (“-out test.bin”). The key, in hexadecimal, is 12345678911234567892123456789312 (“-K …”) and the iv is 00000000 (-“iv …”). Notice that the -K is in capital letters. The lower case k has a different meaning! The key and iv must also expect their parameters to be in hexadecimal. To decrypt the encrypted file on the command line, you also use the enc command. The only differences are that the use ‘-d’ instead of ‘-e’ and you do not need the “-out …” argument.

 openssl enc -e -bf-cbc -in test.bin \
    -K 12345678911234567892123456789312 \
    -iv 00000000 

The functions for performing encryption and decryption in the OpenSSL crypto library are EVP_EncryptInit_ex, EVP_EncryptUpdate and EVP_EncryptFinal_ex, for encryption, and EVP_DecryptInit_ex, EVP_DecryptUpdate and EVP_DecryptFinal_ex, for decryption. The documentation for these functions include an example for how to perform encryption using the encryption functions. The code for decryption is similar to the code for encryption.

First, start by loading the key, iv and the encrypted data.  The encrypted data can be loaded using the standard combination of fopen, fread and fclose.


int readfile(const char *filePath,
             unsigned char * buf,
             int maxSize)
{
    FILE * inf = NULL;
    int readSize = 0;
    inf = fopen(filePath, "rb") ;

    if (inf != NULL)
    {
        readSize = fread(buf, 1,
            maxSize, inf) ;
        fclose(inf);
    }

    return readSize;
}

If you are using encrypted data created using the same key and iv combination as above, the key and iv should be specified as follows:

    char key[16] = {0x12, 0x34, 0x56, 0x78, 0x91, 0x12, 0x34, 0x56,
                     0x78, 0x92, 0x12, 0x34, 0x56, 0x78, 0x93, 0x12};
 
    char iv[8];
    memset(iv, 0x00, 8);

Notice that the key and iv are specified in hexadecimal. This is because when the encrypted data as generated, the key and iv were also given in hex! This is also important, as specifying it has a literal string (ie. char *iv = “00000000”) is not the same and could result in time spent in trying to debug it fustratingly!

As mentioned before, the code for decrypting is similar to the code for encrypting. The first step is to setup a cipher context and initialise it.

char *decrypt (char *key,
               char *iv,
               char *encryptedData,
               int encryptedLength)
{
    // Initialisation
    EVP_CIPHER_CTX *cryptCtx = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(cryptCtx);

    int decryptedLength = 0;
    int allocateSize = encryptedLength * sizeof(char);
    int lastDecryptLength = 0;

    char *decryptedData = (char *) malloc (allocateSize);
    memset(decryptedData, 0x00, allocateSize);

    int decryptResult = EVP_DecryptInit_ex(cryptCtx,
        EVP_bf_cbc(), NULL, key, iv);

Next, use the EVP_DecryptUpdate function to attach the encrypted data and to decrypt the data.

    // EVP_DecryptInit_ex returns 1 if it succeeded.
    if (decryptResult == 1)
    {
        decryptResult = EVP_DecryptUpdate(cryptCtx, decryptedData,
            &decryptedLength, encryptedData, encryptedLength);

Note that EVP_DecryptUpdate will alter the value of the third parameter to be equal to the amount of data that was written. This is not always the entire length of the decrypted data! To finish the decryption process, use EVP_DecryptFinal_ex. This will decrypt any remaining data.

        // Cleanup
        if (decryptResult == 1)
        {
            // Stick the final data at the end of the last
            // decrypted data.
            EVP_DecryptFinal_ex(cryptCtx,
                decryptedData + decryptedLength,
                &lastDecryptLength);

            decryptedLength = decryptedLength + lastDecryptLength;
            decryptedData[decryptedLength – 1] = ;
            printf ("Decrypted size: %d\n", decryptedLength);
            printf ("Decrypted data: \n%s\n\n", decryptedData);
        }
        else
        {
            printf ("EVP_DeccryptUpdate failure.\n");
        }
    }
    else
    {
        printf ("EVP_DecryptInit_ex failure.\n");
    }

    EVP_CIPHER_CTX_free(cryptCtx);
    EVP_cleanup();
    return decryptedData;
}

In the call to EVP_DecryptFinal_ex, note that the pointer that is given is the start of the decrypted memory buffer plus the length that was given back by the call to EVP_DecryptUpdate. This to prevent it from writing over what was originally decrypted. The resulting decrypted data is NOT guaranteed to be always null terminated. This is why a null has also been explicitly set in the above code.