Home

15 Nov 2011

AES Encryption using OpenSSL

AES is a fast block cipher and symmetric encryption standard. It encrypts data using a key. You should always salt, or stretch, a key. A simple string or password is not enough entropy. Instead you’ll derive a key from a user-supplied password by applying random data, called a salt, and then hash it several times over. You’ll use a salt so that if a person uses the same password elsewhere, the output will always be different.

In another article you leanred about hashing. If that is new to you, check out that article first.

Applying Password-Based Key Derivation

You can implement this all yourself, however a key derivation function has already been developed for this purpose – called PBKDF2 (Password-Based Key Derivation Function 2). It performs the functions many times over to stretch the key and help prevent password cracking.

Here is some code to create a 256 bit key using OpenSSL’s PKCS5_PBKDF2_HMAC function. It’s parameters are the following:

  1. The password.
  2. Length of the password.
  3. The salt.
  4. Length of the salt.
  5. Number of iterations to perform.
  6. The specific hash function.
  7. Length of the key.
  8. The out data.

     int iterations = 1000;
     size_t keyLength  = 32; //256 bits
     size_t i;
     unsigned char *keyChar;
     const char passwordChar[] = "qf4i2btz";
     unsigned char saltChar[] = {'q','j','6','-'};
     keyChar = (unsigned char *)malloc(sizeof(unsigned char) * keyLength);
     cout << "password is " << passwordChar << endl;
     cout << "salt is ";
     for (i = 0; i < sizeof(saltChar); i++)
     {
         printf("%02x", saltChar[i]);
     }
     cout << endl;
     if (PKCS5_PBKDF2_HMAC(passwordChar, strlen(passwordChar), saltChar, sizeof(saltChar), iterations, EVP_sha512(), keyLength, keyChar) != 0 )
     {
         cout << "key is ";
         for (i = 0; i < keyLength; i++)
         {
             printf("%02x", keyChar[i]);
         }
         cout << endl;
     }
     else
     {
         cout << "Failure to create key for password - PKCS5_PBKDF2_HMAC_SHA1" << endl;
     }
        
     // ...
        
     free(keyChar);
    

This will always give the same result given the same iterations, salt and password. So if a user enters their password, you would perform this function to get the correct key.

Initialization Vectors

There are different modes of encryption - You’ll use CBC mode. The reason for this is that in CBC mode each next plain text block is XOR’d with the previous cipher text block which makes for stronger encryption. The problem with CBC is that the first block is not as unique. If there are common words in the first block, then you could perform a dictionary attack on it. To get around this, you’ll use an initialization vector (IV) of random bytes that are XOR’d with the first block.

Here is a 128 bit IV:

unsigned char *iv = (unsigned char *)"73472859478267948";

Create the above with a good secure random number generator for your platform. One option is arc4random. Another is SecRandomCopyBytes.

Although the IV is considered public, avoid using constant or sequential IV’s. Do not reuse an IV for more than one message if you are using the same key.

Encrypting The Data

To begin, create a context and initialize it:

EVP_CIPHER_CTX *cipherContext;
cipherContext = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(cipherContext);

Next set up the type of encryption operation you’re are doing, in this case AES. Lookup the correct values for the standard. AES-256 uses a 256 bit key, and 128 bit IV size (AES256 refers to the key size. The block and IV size are still 128 bits). The following function performs key setup:

EVP_EncryptInit_ex(cipherContext, EVP_aes_256_cbc(), NULL, keyChar, ivChar);

Use the update function to provide cleartext data. You can call it multiple times to append unencrypted data.

EVP_EncryptUpdate(cipherContext, cipherTextChar, &length, plainTextChar, plainTextLength);

When you’re finished adding data, call the finalize function. This will finish writing all encrypted bytes:

EVP_EncryptFinal_ex(cipherContext, cipherTextChar + length, &length);

The EVP methods are high level functions of the OpenSSL library. While you can access the same functionality using raw lower level parts of the API, it is not recommended as it may introduce more security holes if you are not careful with the implementation. The previous functions will return 1 for success and 0 for failure.

Here is the full function:

#include <openssl/evp.h>
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/x509.h>
#include <openssl/hmac.h>

int Crypto::_encrypt(unsigned char *plainTextChar, int plainTextLength, unsigned char *keyChar, unsigned char *ivChar, unsigned char *cipherTextChar)
{
    EVP_CIPHER_CTX *cipherContext;
    int length;
    int cipherTextLength;
    
    if ( ! (cipherContext = EVP_CIPHER_CTX_new()) )
    {
        _handleErrors();
    }
    EVP_CIPHER_CTX_init(cipherContext);
    
    if (1 != EVP_EncryptInit_ex(cipherContext, EVP_aes_256_cbc(), NULL, keyChar, ivChar) )
    {
        _handleErrors();
    }
    
    if (1 != EVP_EncryptUpdate(cipherContext, cipherTextChar, &length, plainTextChar, plainTextLength))
    {
        _handleErrors();
    }
    
    cipherTextLength = length;

    if (1 != EVP_EncryptFinal_ex(cipherContext, cipherTextChar + length, &length))
    {
        _handleErrors();
    }
    
    cipherTextLength += length;
    
    EVP_CIPHER_CTX_cleanup(cipherContext);
    EVP_CIPHER_CTX_free(cipherContext);
    
    return cipherTextLength;
}

Padding is used by default if the data does not divide exactly into the fixed block size.

Decrypting The Data

To decrypt the data, create and initialize the context with a decryption operation using the same key size:

cipherContext = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(cipherContext, EVP_aes_256_cbc(), NULL, keyChar, ivChar);
EVP_CIPHER_CTX_init(cipherContext);

As before, you can call the decrypted operation on multiple input sources:

EVP_DecryptUpdate(cipherContext, plainTextChar, &length, cipherTextChar, cipherTextLength);

Call this to write the final plaintext bytes:

EVP_DecryptFinal_ex(cipherContext, plainTextChar + length, &length);

Here is the full decrypt function:

int Crypto::_decrypt(unsigned char *cipherTextChar, int cipherTextLength, unsigned char *keyChar, unsigned char *ivChar, unsigned char *plainTextChar)
{
    EVP_CIPHER_CTX *cipherContext;
    int length;
    int plainTextLength;
    
    if ( ! (cipherContext = EVP_CIPHER_CTX_new()) )
    {
        _handleErrors();
    }
    EVP_CIPHER_CTX_init(cipherContext);
    
    if (1 != EVP_DecryptInit_ex(cipherContext, EVP_aes_256_cbc(), NULL, keyChar, ivChar))
    {
        _handleErrors();
    }

    if (1 != EVP_DecryptUpdate(cipherContext, plainTextChar, &length, cipherTextChar, cipherTextLength))
    {
        _handleErrors();
    }
    plainTextLength = length;
    
 
    if (1 != EVP_DecryptFinal_ex(cipherContext, plainTextChar + length, &length))
    {
        _handleErrors();
    }
    plainTextLength += length;
    
    EVP_CIPHER_CTX_cleanup(cipherContext);
    EVP_CIPHER_CTX_free(cipherContext);
    
    return plainTextLength;
}

Here is the _handleErrors() function used in the above code:

void Crypto::_handleErrors(void)
{
    ERR_print_errors_fp(stderr);
    abort();
}

Putting it All Together

There are a few more things you should know about the functions:

Make a test function by wrapping the C code in a higher level C++ class, call it Crypto. You could additionally pass std::string in and out of the class if you want. The following example adds the extra convenience of encoding and decoding the binary data to and from Base64. If you don’t feel like adding a Base64 utility to your code base, you can comment out that optional step below:

void Crypto::encryptTest()
{
    //Can be changed to generate a key from the given password
    int iterations = 1000;
    size_t keyLength  = 32; //256 bits
    size_t i;
    unsigned char *keyChar;
    const char passwordChar[] = "qf4i2btz";
    unsigned char saltChar[] = {'q','j','6','-'};
    keyChar = (unsigned char *)malloc(sizeof(unsigned char) * keyLength);
    //Remove the following in production code:
    cout << "password is " << passwordChar << endl;
    cout << "salt is ";
    for (i = 0; i < sizeof(saltChar); i++)
    {
        printf("%02x", saltChar[i]);
    }
    cout << endl;
    if (PKCS5_PBKDF2_HMAC(passwordChar, strlen(passwordChar), saltChar, sizeof(saltChar), iterations, EVP_sha512(), keyLength, keyChar) != 0 )
    {
        //Comment out for production code:
        cout << "key is ";
        for (i = 0; i < keyLength; i++)
        {
            printf("%02x", keyChar[i]);
        }
        cout << endl;
    }
    else
    {
        cout << "Failure to create key for password - PKCS5_PBKDF2_HMAC_SHA1" << endl;
    }

    unsigned char *ivChar = (unsigned char *)"73472859478267948"; //128 bit IV - use a secure generator
    unsigned char *plainTextChar = (unsigned char *)"Secret text to be encrypted";

    //Test buffer for cipher text. For this test we must make sure this is long enough for the
    //encrypted data. It could end up being longer than plain text
    unsigned char cipherTextChar[128];

    unsigned char decryptedTextChar[128];

    int decryptedTextLength, cipherTextLength;

    //Init OpenSSL library
    ERR_load_crypto_strings();
    OpenSSL_add_all_algorithms();
    OPENSSL_config(NULL);

    //do the actual encryption
    cipherTextLength = _encrypt(plainTextChar, strlen((const char *)plainTextChar), keyChar, ivChar, cipherTextChar);

    
    
    
    //-----
    //Optional example, this part can be skipped. Convert binary data to Base64 string
    //If your storing the data as binary, then you can pass in binary data to be decrypted
    std::string base64EncryptedString = Base64::base64Encode(cipherTextChar, cipherTextLength);
    cout << "Cipher text is " << base64EncryptedString << endl;
    
    //Now convert the Base64 string back to data
    unsigned char encryptedDataChar[128];
    Base64::base64DecodeToData(base64EncryptedString, encryptedDataChar, cipherTextLength);
    //-----
    
    
    
    

    //do the actual decryption
    decryptedTextLength = _decrypt(cipherTextChar, cipherTextLength, keyChar, ivChar, decryptedTextChar);

    //We know we passed in text at the beginning, so add a NULL terminator to be compliant with printable text
    decryptedTextChar[decryptedTextLength] = '\0';

    //wrap it into something useful or higher level...
    std::string decryptedString((char *)decryptedTextChar, decryptedTextLength);
    cout << "Decrypted text is:" << decryptedString << endl;
    
    //cleanup
    EVP_cleanup();
    ERR_free_strings();
    CONF_modules_free();
    free(keyChar);
}

While this example knows the length of the plain text and output, you can figure out the length of the output by ( (inputLength / blockSize) * blockSize) + blockSize.

Remember to remove all console logging for production code.

That’s it for implementing AES 256 CBC in OpenSSL. To learn how to perform AES encryption on other mobile platforms, check out the iOS Encryption Tutorial and Encryption Tutorial for Android.

If you’re interested in learning more about other popular modes of operation, check out AES GCM.