Tutorial: AES Encryption and Decryption with OpenSSL

Tutorial: AES Encryption and Decryption with OpenSSL

AES (Advanced Encryption Standard) is a symmetric-key encryption algorithm. Symmetric-key algorithms are algorithms for cryptography that use the same cryptographic keys for both encryption of plaintext and decryption of ciphertext. In this tutorial we will demonstrate how to encrypt plaintext using the OpenSSL command line and decrypt the cipher using the OpenSSL C++ API.

The cryptographic keys used for AES are usually fixed-length (for example, 128 or 256bit keys). Because humans cannot easily remember long random strings, key stretching is performed to create a long, fixed-length key from a short, variable length password. Key stretching uses a key-derivation function. Command line OpenSSL uses a rather simplistic method for computing the cryptographic key from a password, which we will need to mimic using the C++ API. OpenSSL uses a hash of the password and a random 64bit salt. Only a single iteration is performed.

Encrypting: OpenSSL Command Line

To encrypt a plaintext using AES with OpenSSL, the enc command is used. The following command will prompt you for a password, encrypt a file called plaintext.txt and Base64 encode the output. The output will be written to standard out (the console). SHA1 will be used as the key-derivation function. We will use the password 12345 in this example.

$ openssl enc -aes-256-cbc -in plaintext.txt -base64 -md sha1

This will result in a different output each time it is run. This is because a different (random) salt is used. The Salt is written as part of the output, and we will read it back in the next section.

Decrypting: OpenSSL API

To decrypt the output of an AES encryption (aes-256-cbc) we will use the OpenSSL C++ API. Unlike the command line, each step must be explicitly performed with the API. There are four steps involved when decrypting: 1) Decoding the input (from Base64), 2) extracting the Salt, 3) creating the key (key-stretching) using the password and the Salt, and 4) performing the AES decryption.

Base64 Decoding

When the plaintext was encrypted, we specified -base64. This resulted in a Base64 encoding of the output which is important if you wish to process the cipher with a text editor or read it into a string. Before decryption can be performed, the output must be decoded from its Base64 representation. We use the same decoding algorithm that we used in our previous OpenSSL Tutorial:

size_t calcDecodeLength(char* b64input) {
  size_t len = strlen(b64input), padding = 0;

  if (b64input[len-1] == '=' && b64input[len-2] == '=') //last two chars are =
    padding = 2;
  else if (b64input[len-1] == '=') //last char is =
    padding = 1;
  return (len*3)/4 - padding;
}

void Base64Decode( char* b64message, unsigned char** buffer, size_t* length) {
  BIO *bio, *b64;

  int decodeLen = calcDecodeLength(b64message);
  *buffer = (unsigned char*)malloc(decodeLen + 1);
  (*buffer)[decodeLen] = '\0';

  bio = BIO_new_mem_buf(b64message, -1);
  b64 = BIO_new(BIO_f_base64());
  bio = BIO_push(b64, bio);

  *length = BIO_read(bio, *buffer, strlen(b64message));
  BIO_free_all(bio);
}

 

Again, special thanks to Barry Steyn for providing this.

Extracting the Salt

Once we have decoded the cipher, we can read the salt. The Salt is identified by the 8 byte header (Salted__), followed by the 8 byte salt. We start by ensuring the header exists, and then we extract the following 8 bytes:

unsigned char* ciphertext = ...; // Decoded cipher text
size_t cipher_len = ...; // Length of decoded cipher text, computed during Base64Decode
if (strncmp((const char*)ciphertext,"Salted__",8) == 0) {
  memcpy(salt,&ciphertext[8],8);
  ciphertext += 16;
  cipher_len -= 16;
}

We then move the ciphertext pointer 16 character into the string, and reduce the length of the cipher text by 16.

Computing the Key and Initialization Vector

Once we have extracted the salt, we can use the salt and password to generate the Key and Initialization Vector (IV). To determine the Key and IV from the password (and key-derivation function) use the EVP_BytesToKey function:

void initAES(const string& pass, unsigned char* salt, unsigned char* key, unsigned char* iv )
{
  bzero(key,sizeof(key));
  bzero(iv,sizeof(iv));
  EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), salt, (unsigned char*)pass.c_str(), pass.length(), 1, key, iv);
}

 

This initially zeros out the Key and IV, and then uses the EVP_BytesToKey to populate these two data structures. In this case we are using Sha1 as the key-derivation function and the same password used when we encrypted the plaintext. We use a single iteration (the 6th parameter).

Decrypting the Cipher

With the Key and IV computed, and the cipher decoded from Base64, we are now ready to decrypt the message. To decrypt the message we need a buffer in which to store it. Since the cipher text is always greater (or equal to) the length of the plaintext, we can allocate a buffer with the same length as the ciphertext. OpenSSL will tell us exactly how much data it wrote to that buffer.

string decrypt(unsigned char *ciphertext, 
               int ciphertext_len, 
               unsigned char *key,
               unsigned char *iv ) {

  EVP_CIPHER_CTX *ctx;
  unsigned char *plaintexts;
  int len;
  int plaintext_len;
  unsigned char* plaintext = new unsigned char[ciphertext_len];
  bzero(plaintext,ciphertext_len);

  /* Create and initialise the context */
  if(!(ctx = EVP_CIPHER_CTX_new())) handleOpenSSLErrors();

  /* Initialise the decryption operation. IMPORTANT - ensure you use a key
   * and IV size appropriate for your cipher
   * In this example we are using 256 bit AES (i.e. a 256 bit key). The
   * IV size for *most* modes is the same as the block size. For AES this
   * is 128 bits */
  if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
    handleOpenSSLErrors();

  EVP_CIPHER_CTX_set_key_length(ctx, EVP_MAX_KEY_LENGTH);

  /* Provide the message to be decrypted, and obtain the plaintext output.
   * EVP_DecryptUpdate can be called multiple times if necessary
   */
  if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
    handleOpenSSLErrors();

  plaintext_len = len;

  /* Finalize the decryption. Further plaintext bytes may be written at
   * this stage.
   */
  int pad_len;
  if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) handleOpenSSLErrors();
  plaintext_len += len;

  /* Add the null terminator */
  plaintext[plaintext_len] = 0;

  /* Clean up */
  EVP_CIPHER_CTX_free(ctx);
  string ret = (char*)plaintext;
  delete [] plaintext;
  return ret;
}

We begin by initializing the Decryption with the AES algorithm, Key and IV. We then pass the EVP_DecryptUpdate function the ciphertext, a buffer for the plaintext and a pointer to the length. This will perform the decryption and can be called several times if you wish to decrypt the cipher in blocks. Finally, calling EVP_DecryptFinal_ex will complete the decryption. We null terminate the plaintext buffer at the end of the input and return the result.

Conclusion

In this tutorial we demonstrated how to encrypt a message using the OpenSSL command line and then how to decrypt the message using the OpenSSL C++ API. The API required a bit more work as we had to manually decode the cipher, extract the salt, compute the Key and perform the decryption. A complete copy of the code for this tutorial can be found here.

3 Comments
  • Frank
    Reply
    Posted at 10:53 pm, September 8, 2017

    Hello Ian … does this only work with plain text? How could this be modified to work with enc/dec audio files (i.e., .mp3) ?

    • Ian
      Reply
      Posted at 5:41 pm, September 11, 2017

      @Frank. This technique is not limited to plaintext. For binary files (such as mp3s) I would first base64 encode the file, which will produce a text file, and then apply the same procedure.

  • Frank
    Reply
    Posted at 12:01 am, September 12, 2017

    Thank you Ian … that worked!

Post a Comment

Comment
Name
Email
Website