Encrypt/Decrypt with AES-GCM using OpenSSL












0















I am writing an AES-GCM class for my application. The requirements constrain me to C++98 and static keys. I am having trouble unit testing the code I wrote. I am not able encrypt a test string and decrypt it back to its original form. What am I doing wrong here using OpenSSL EVP?



AES class, here are the juicy bits:



/**
* @brief ctor for aescipher class
*
* @params secret secret cryptographic key
* @params secret_len array length of secret
* @params iv initialization vector (note, For a given key, the
* IV MUST NOT repeat.)
* @params iv_len array length of iv
*/
aescipher::aescipher(const uint8_t* secret, size_t secret_len,
const uint8_t* iv, size_t iv_len)
: mSecretSize(secret_len)
, mSecret(new uint8_t[secret_len])
, mIVSize(iv_len)
, mIV(new uint8_t[iv_len])
{
memcpy(mSecret, secret, mSecretSize);
memcpy(mIV, iv, mIVSize);
}

aescipher::~aescipher()
{
delete mSecret;
mSecret = NULL;

delete mIV;
mIV = NULL;
}

/**
* @brief generates a random 16-octet array suitable to use as an AES-GCM integrity check
*
* @param iv unsigned 8-octet array for iv
*
* @returns boolean - RAND_bytes() success
*/
/* static */ bool aescipher::generateIV(uint8_t iv[8])
{
return (RAND_bytes(iv, 8) == 1);
}

/**
* @brief encrypt data and aad using AES-GCM
*
* @param src data to be encrypted
* @param src_len array length of src
* @param aad additional authenticated data included but not encrypted (optional/nullable)
* @param aad_len array length of aad
* @param dst destination data array for encrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
const uint8_t *aad, size_t aad_size,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int cipher_len = 0;
int len = 0;
EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_EncryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);
// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_EncryptInit_ex(context, NULL, NULL, mSecret, mIV);
// Provide AAD if supplied
if (aad && aad_size) {
if (!EVP_EncryptUpdate(context, NULL, &len, aad, aad_size))
goto AES_ERROR;
}

if (!EVP_EncryptUpdate(context, dst, &len, src, src_len)) {
goto AES_ERROR;
}
cipher_len = len;

// There may be some final data left to encrypt if the input is
// not an exact multiple of the block size.
if (!EVP_EncryptFinal_ex(context, dst + len, &len))
goto AES_ERROR;
//Normally ciphertext bytes may be written during finalization,
// but this does not occur in GCM mode, so we make do manually.
cipher_len += len;
// Get the tag
if (!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, 16, tag))
goto AES_ERROR;

EVP_CIPHER_CTX_free(context);
return cipher_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}

/**
* @brief convenience encrypt() function omitting AAD params
*
* @param src data to be encrypted
* @param src_len array length of src
* @param dst destination data array for encrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
return encrypt(src, src_len, NULL, 0, dst, dst_len, tag);
}


/**
* @brief decrypt AES-GCM encrypted data
*
* @param src encrypted data array
* @param src_len array length of src
* @param dst destination array of decrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
* @param aad additional authenticated data received
* @param aad_len array length of aad
*
* @return bytes written to dst
*/
size_t aescipher::decrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t* aad, size_t aad_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int output_len = 0;
int temp_len = 0;

EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_DecryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);

// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_DecryptInit_ex(context, NULL, NULL, mSecret, mIV);

// Provide AAD if supplied
if (!EVP_DecryptUpdate(context, NULL, &temp_len, aad, aad_len)) {
goto AES_ERROR;
}
if (!EVP_DecryptUpdate(context, dst, &temp_len, src, src_len)) {
goto AES_ERROR;
}
output_len = temp_len;

// Set expected tag value.
if(!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, 16, tag)) {
goto AES_ERROR;
}
// Finalise the decryption. A positive return value indicates success,
// anything else is a failure - the plaintext is not trustworthy.
if (EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len) <= 0){
goto AES_ERROR;
}
output_len += temp_len;

EVP_CIPHER_CTX_free(context);
return output_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}


Now, for the unit test.



FYI, I am not generating an IV for the unit test because I have Base64 encoded the encrypted data to store in the unit test source, therefore, I need to use a static IV with it. I just need to test the class. Don't worry, generating a unique IV will happen at a higher level. There is no AAD at this point in the tests.



Base64 and all undefined classes are unit tested and functional, so their definitions are irrelevant to the question.



Secret key I'm using is:



const unsigned char *secret = reinterpret_cast<const unsigned char*>("Open** Sesame**");


Encrypt test (passes tests, but encrypted_data may actually be invalid data!):



const unsigned char *msg = reinterpret_cast<const unsigned char*>(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encrypted_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
const size_t msg_len = 127;
size_t ciphertext_len = 256;
unsigned char ciphertext[ciphertext_len];
memset(ciphertext, 0, sizeof(ciphertext));

uint8_t iv[8], tag[16];
memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.encrypt(msg, msg_len, ciphertext, ciphertext_len, tag);

unsigned char encrypted_data[ciphertext_len];
(void)Base64::decode(encrypted_msg.c_str(), encrypted_msg.length(), encrypted_data, ciphertext_len);

TK_assertTrue(memcmp(ciphertext, encrypted_data, bytes) == 0);
TK_assertTrue(bytes == msg_len);


Decrypt test (does not pass):



std::string decrypted_msg(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encoded_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
std::string gcm_tag("hDlT5X7CGPwoPmVZSP2ezQ==");
const size_t msg_len = 127;
uint8_t iv[8], tag[16], enc_msg[msg_len], msg[msg_len], aad[msg_len];

// decode encrypted data
Base64::decode(encoded_msg.c_str(), encoded_msg.length(), enc_msg, msg_len);
Base64::decode(gcm_tag.c_str(), gcm_tag.length(), tag, sizeof(tag));

memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.decrypt(enc_msg, msg_len, msg, msg_len, aad, msg_len, tag);
TK_assertTrue(bytes == msg_len);
TK_assertTrue(strncmp(reinterpret_cast<const char*>(msg), decrypted_msg.c_str(), bytes) == 0);


Note, on the decrypt unit test, aescipher::decrypt fails in EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len).










share|improve this question




















  • 1





    You use EVP_aes_256_gcm in EVP_EncryptInit_ex but EVP_aes_128_gcm in EVP_DecryptInit_ex

    – Marek Klein
    Jan 2 at 11:30











  • Great catch! Thank you. I have updated the code to use EVP_aes_128_gcm for both encrypt and decrypt but decrypt is still failing.

    – Cinder Biscuits
    Jan 2 at 12:02











  • Can you share your secret key? I happened to have a very similar test so I can cross check your test vectors.

    – Marek Klein
    Jan 2 at 13:12






  • 1





    First try to make your secret key to be 16 bytes long. It is 12 bytes but AES 128 uses 16 bytes long keys so I suspect that you use 4 "random" bytes in tour key.

    – Marek Klein
    Jan 2 at 13:47






  • 1





    Grate to hear that. You may find this useful in future.

    – Marek Klein
    Jan 2 at 14:47


















0















I am writing an AES-GCM class for my application. The requirements constrain me to C++98 and static keys. I am having trouble unit testing the code I wrote. I am not able encrypt a test string and decrypt it back to its original form. What am I doing wrong here using OpenSSL EVP?



AES class, here are the juicy bits:



/**
* @brief ctor for aescipher class
*
* @params secret secret cryptographic key
* @params secret_len array length of secret
* @params iv initialization vector (note, For a given key, the
* IV MUST NOT repeat.)
* @params iv_len array length of iv
*/
aescipher::aescipher(const uint8_t* secret, size_t secret_len,
const uint8_t* iv, size_t iv_len)
: mSecretSize(secret_len)
, mSecret(new uint8_t[secret_len])
, mIVSize(iv_len)
, mIV(new uint8_t[iv_len])
{
memcpy(mSecret, secret, mSecretSize);
memcpy(mIV, iv, mIVSize);
}

aescipher::~aescipher()
{
delete mSecret;
mSecret = NULL;

delete mIV;
mIV = NULL;
}

/**
* @brief generates a random 16-octet array suitable to use as an AES-GCM integrity check
*
* @param iv unsigned 8-octet array for iv
*
* @returns boolean - RAND_bytes() success
*/
/* static */ bool aescipher::generateIV(uint8_t iv[8])
{
return (RAND_bytes(iv, 8) == 1);
}

/**
* @brief encrypt data and aad using AES-GCM
*
* @param src data to be encrypted
* @param src_len array length of src
* @param aad additional authenticated data included but not encrypted (optional/nullable)
* @param aad_len array length of aad
* @param dst destination data array for encrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
const uint8_t *aad, size_t aad_size,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int cipher_len = 0;
int len = 0;
EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_EncryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);
// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_EncryptInit_ex(context, NULL, NULL, mSecret, mIV);
// Provide AAD if supplied
if (aad && aad_size) {
if (!EVP_EncryptUpdate(context, NULL, &len, aad, aad_size))
goto AES_ERROR;
}

if (!EVP_EncryptUpdate(context, dst, &len, src, src_len)) {
goto AES_ERROR;
}
cipher_len = len;

// There may be some final data left to encrypt if the input is
// not an exact multiple of the block size.
if (!EVP_EncryptFinal_ex(context, dst + len, &len))
goto AES_ERROR;
//Normally ciphertext bytes may be written during finalization,
// but this does not occur in GCM mode, so we make do manually.
cipher_len += len;
// Get the tag
if (!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, 16, tag))
goto AES_ERROR;

EVP_CIPHER_CTX_free(context);
return cipher_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}

/**
* @brief convenience encrypt() function omitting AAD params
*
* @param src data to be encrypted
* @param src_len array length of src
* @param dst destination data array for encrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
return encrypt(src, src_len, NULL, 0, dst, dst_len, tag);
}


/**
* @brief decrypt AES-GCM encrypted data
*
* @param src encrypted data array
* @param src_len array length of src
* @param dst destination array of decrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
* @param aad additional authenticated data received
* @param aad_len array length of aad
*
* @return bytes written to dst
*/
size_t aescipher::decrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t* aad, size_t aad_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int output_len = 0;
int temp_len = 0;

EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_DecryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);

// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_DecryptInit_ex(context, NULL, NULL, mSecret, mIV);

// Provide AAD if supplied
if (!EVP_DecryptUpdate(context, NULL, &temp_len, aad, aad_len)) {
goto AES_ERROR;
}
if (!EVP_DecryptUpdate(context, dst, &temp_len, src, src_len)) {
goto AES_ERROR;
}
output_len = temp_len;

// Set expected tag value.
if(!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, 16, tag)) {
goto AES_ERROR;
}
// Finalise the decryption. A positive return value indicates success,
// anything else is a failure - the plaintext is not trustworthy.
if (EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len) <= 0){
goto AES_ERROR;
}
output_len += temp_len;

EVP_CIPHER_CTX_free(context);
return output_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}


Now, for the unit test.



FYI, I am not generating an IV for the unit test because I have Base64 encoded the encrypted data to store in the unit test source, therefore, I need to use a static IV with it. I just need to test the class. Don't worry, generating a unique IV will happen at a higher level. There is no AAD at this point in the tests.



Base64 and all undefined classes are unit tested and functional, so their definitions are irrelevant to the question.



Secret key I'm using is:



const unsigned char *secret = reinterpret_cast<const unsigned char*>("Open** Sesame**");


Encrypt test (passes tests, but encrypted_data may actually be invalid data!):



const unsigned char *msg = reinterpret_cast<const unsigned char*>(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encrypted_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
const size_t msg_len = 127;
size_t ciphertext_len = 256;
unsigned char ciphertext[ciphertext_len];
memset(ciphertext, 0, sizeof(ciphertext));

uint8_t iv[8], tag[16];
memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.encrypt(msg, msg_len, ciphertext, ciphertext_len, tag);

unsigned char encrypted_data[ciphertext_len];
(void)Base64::decode(encrypted_msg.c_str(), encrypted_msg.length(), encrypted_data, ciphertext_len);

TK_assertTrue(memcmp(ciphertext, encrypted_data, bytes) == 0);
TK_assertTrue(bytes == msg_len);


Decrypt test (does not pass):



std::string decrypted_msg(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encoded_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
std::string gcm_tag("hDlT5X7CGPwoPmVZSP2ezQ==");
const size_t msg_len = 127;
uint8_t iv[8], tag[16], enc_msg[msg_len], msg[msg_len], aad[msg_len];

// decode encrypted data
Base64::decode(encoded_msg.c_str(), encoded_msg.length(), enc_msg, msg_len);
Base64::decode(gcm_tag.c_str(), gcm_tag.length(), tag, sizeof(tag));

memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.decrypt(enc_msg, msg_len, msg, msg_len, aad, msg_len, tag);
TK_assertTrue(bytes == msg_len);
TK_assertTrue(strncmp(reinterpret_cast<const char*>(msg), decrypted_msg.c_str(), bytes) == 0);


Note, on the decrypt unit test, aescipher::decrypt fails in EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len).










share|improve this question




















  • 1





    You use EVP_aes_256_gcm in EVP_EncryptInit_ex but EVP_aes_128_gcm in EVP_DecryptInit_ex

    – Marek Klein
    Jan 2 at 11:30











  • Great catch! Thank you. I have updated the code to use EVP_aes_128_gcm for both encrypt and decrypt but decrypt is still failing.

    – Cinder Biscuits
    Jan 2 at 12:02











  • Can you share your secret key? I happened to have a very similar test so I can cross check your test vectors.

    – Marek Klein
    Jan 2 at 13:12






  • 1





    First try to make your secret key to be 16 bytes long. It is 12 bytes but AES 128 uses 16 bytes long keys so I suspect that you use 4 "random" bytes in tour key.

    – Marek Klein
    Jan 2 at 13:47






  • 1





    Grate to hear that. You may find this useful in future.

    – Marek Klein
    Jan 2 at 14:47
















0












0








0








I am writing an AES-GCM class for my application. The requirements constrain me to C++98 and static keys. I am having trouble unit testing the code I wrote. I am not able encrypt a test string and decrypt it back to its original form. What am I doing wrong here using OpenSSL EVP?



AES class, here are the juicy bits:



/**
* @brief ctor for aescipher class
*
* @params secret secret cryptographic key
* @params secret_len array length of secret
* @params iv initialization vector (note, For a given key, the
* IV MUST NOT repeat.)
* @params iv_len array length of iv
*/
aescipher::aescipher(const uint8_t* secret, size_t secret_len,
const uint8_t* iv, size_t iv_len)
: mSecretSize(secret_len)
, mSecret(new uint8_t[secret_len])
, mIVSize(iv_len)
, mIV(new uint8_t[iv_len])
{
memcpy(mSecret, secret, mSecretSize);
memcpy(mIV, iv, mIVSize);
}

aescipher::~aescipher()
{
delete mSecret;
mSecret = NULL;

delete mIV;
mIV = NULL;
}

/**
* @brief generates a random 16-octet array suitable to use as an AES-GCM integrity check
*
* @param iv unsigned 8-octet array for iv
*
* @returns boolean - RAND_bytes() success
*/
/* static */ bool aescipher::generateIV(uint8_t iv[8])
{
return (RAND_bytes(iv, 8) == 1);
}

/**
* @brief encrypt data and aad using AES-GCM
*
* @param src data to be encrypted
* @param src_len array length of src
* @param aad additional authenticated data included but not encrypted (optional/nullable)
* @param aad_len array length of aad
* @param dst destination data array for encrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
const uint8_t *aad, size_t aad_size,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int cipher_len = 0;
int len = 0;
EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_EncryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);
// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_EncryptInit_ex(context, NULL, NULL, mSecret, mIV);
// Provide AAD if supplied
if (aad && aad_size) {
if (!EVP_EncryptUpdate(context, NULL, &len, aad, aad_size))
goto AES_ERROR;
}

if (!EVP_EncryptUpdate(context, dst, &len, src, src_len)) {
goto AES_ERROR;
}
cipher_len = len;

// There may be some final data left to encrypt if the input is
// not an exact multiple of the block size.
if (!EVP_EncryptFinal_ex(context, dst + len, &len))
goto AES_ERROR;
//Normally ciphertext bytes may be written during finalization,
// but this does not occur in GCM mode, so we make do manually.
cipher_len += len;
// Get the tag
if (!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, 16, tag))
goto AES_ERROR;

EVP_CIPHER_CTX_free(context);
return cipher_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}

/**
* @brief convenience encrypt() function omitting AAD params
*
* @param src data to be encrypted
* @param src_len array length of src
* @param dst destination data array for encrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
return encrypt(src, src_len, NULL, 0, dst, dst_len, tag);
}


/**
* @brief decrypt AES-GCM encrypted data
*
* @param src encrypted data array
* @param src_len array length of src
* @param dst destination array of decrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
* @param aad additional authenticated data received
* @param aad_len array length of aad
*
* @return bytes written to dst
*/
size_t aescipher::decrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t* aad, size_t aad_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int output_len = 0;
int temp_len = 0;

EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_DecryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);

// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_DecryptInit_ex(context, NULL, NULL, mSecret, mIV);

// Provide AAD if supplied
if (!EVP_DecryptUpdate(context, NULL, &temp_len, aad, aad_len)) {
goto AES_ERROR;
}
if (!EVP_DecryptUpdate(context, dst, &temp_len, src, src_len)) {
goto AES_ERROR;
}
output_len = temp_len;

// Set expected tag value.
if(!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, 16, tag)) {
goto AES_ERROR;
}
// Finalise the decryption. A positive return value indicates success,
// anything else is a failure - the plaintext is not trustworthy.
if (EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len) <= 0){
goto AES_ERROR;
}
output_len += temp_len;

EVP_CIPHER_CTX_free(context);
return output_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}


Now, for the unit test.



FYI, I am not generating an IV for the unit test because I have Base64 encoded the encrypted data to store in the unit test source, therefore, I need to use a static IV with it. I just need to test the class. Don't worry, generating a unique IV will happen at a higher level. There is no AAD at this point in the tests.



Base64 and all undefined classes are unit tested and functional, so their definitions are irrelevant to the question.



Secret key I'm using is:



const unsigned char *secret = reinterpret_cast<const unsigned char*>("Open** Sesame**");


Encrypt test (passes tests, but encrypted_data may actually be invalid data!):



const unsigned char *msg = reinterpret_cast<const unsigned char*>(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encrypted_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
const size_t msg_len = 127;
size_t ciphertext_len = 256;
unsigned char ciphertext[ciphertext_len];
memset(ciphertext, 0, sizeof(ciphertext));

uint8_t iv[8], tag[16];
memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.encrypt(msg, msg_len, ciphertext, ciphertext_len, tag);

unsigned char encrypted_data[ciphertext_len];
(void)Base64::decode(encrypted_msg.c_str(), encrypted_msg.length(), encrypted_data, ciphertext_len);

TK_assertTrue(memcmp(ciphertext, encrypted_data, bytes) == 0);
TK_assertTrue(bytes == msg_len);


Decrypt test (does not pass):



std::string decrypted_msg(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encoded_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
std::string gcm_tag("hDlT5X7CGPwoPmVZSP2ezQ==");
const size_t msg_len = 127;
uint8_t iv[8], tag[16], enc_msg[msg_len], msg[msg_len], aad[msg_len];

// decode encrypted data
Base64::decode(encoded_msg.c_str(), encoded_msg.length(), enc_msg, msg_len);
Base64::decode(gcm_tag.c_str(), gcm_tag.length(), tag, sizeof(tag));

memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.decrypt(enc_msg, msg_len, msg, msg_len, aad, msg_len, tag);
TK_assertTrue(bytes == msg_len);
TK_assertTrue(strncmp(reinterpret_cast<const char*>(msg), decrypted_msg.c_str(), bytes) == 0);


Note, on the decrypt unit test, aescipher::decrypt fails in EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len).










share|improve this question
















I am writing an AES-GCM class for my application. The requirements constrain me to C++98 and static keys. I am having trouble unit testing the code I wrote. I am not able encrypt a test string and decrypt it back to its original form. What am I doing wrong here using OpenSSL EVP?



AES class, here are the juicy bits:



/**
* @brief ctor for aescipher class
*
* @params secret secret cryptographic key
* @params secret_len array length of secret
* @params iv initialization vector (note, For a given key, the
* IV MUST NOT repeat.)
* @params iv_len array length of iv
*/
aescipher::aescipher(const uint8_t* secret, size_t secret_len,
const uint8_t* iv, size_t iv_len)
: mSecretSize(secret_len)
, mSecret(new uint8_t[secret_len])
, mIVSize(iv_len)
, mIV(new uint8_t[iv_len])
{
memcpy(mSecret, secret, mSecretSize);
memcpy(mIV, iv, mIVSize);
}

aescipher::~aescipher()
{
delete mSecret;
mSecret = NULL;

delete mIV;
mIV = NULL;
}

/**
* @brief generates a random 16-octet array suitable to use as an AES-GCM integrity check
*
* @param iv unsigned 8-octet array for iv
*
* @returns boolean - RAND_bytes() success
*/
/* static */ bool aescipher::generateIV(uint8_t iv[8])
{
return (RAND_bytes(iv, 8) == 1);
}

/**
* @brief encrypt data and aad using AES-GCM
*
* @param src data to be encrypted
* @param src_len array length of src
* @param aad additional authenticated data included but not encrypted (optional/nullable)
* @param aad_len array length of aad
* @param dst destination data array for encrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
const uint8_t *aad, size_t aad_size,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int cipher_len = 0;
int len = 0;
EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_EncryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);
// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_EncryptInit_ex(context, NULL, NULL, mSecret, mIV);
// Provide AAD if supplied
if (aad && aad_size) {
if (!EVP_EncryptUpdate(context, NULL, &len, aad, aad_size))
goto AES_ERROR;
}

if (!EVP_EncryptUpdate(context, dst, &len, src, src_len)) {
goto AES_ERROR;
}
cipher_len = len;

// There may be some final data left to encrypt if the input is
// not an exact multiple of the block size.
if (!EVP_EncryptFinal_ex(context, dst + len, &len))
goto AES_ERROR;
//Normally ciphertext bytes may be written during finalization,
// but this does not occur in GCM mode, so we make do manually.
cipher_len += len;
// Get the tag
if (!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, 16, tag))
goto AES_ERROR;

EVP_CIPHER_CTX_free(context);
return cipher_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}

/**
* @brief convenience encrypt() function omitting AAD params
*
* @param src data to be encrypted
* @param src_len array length of src
* @param dst destination data array for encrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
return encrypt(src, src_len, NULL, 0, dst, dst_len, tag);
}


/**
* @brief decrypt AES-GCM encrypted data
*
* @param src encrypted data array
* @param src_len array length of src
* @param dst destination array of decrypted data
* @param dst_len array length of dst
* @param tag 16 octet data array for Authentication Tag (Integrity Check Value per spec)
* @param aad additional authenticated data received
* @param aad_len array length of aad
*
* @return bytes written to dst
*/
size_t aescipher::decrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t* aad, size_t aad_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int output_len = 0;
int temp_len = 0;

EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_DecryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);

// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_DecryptInit_ex(context, NULL, NULL, mSecret, mIV);

// Provide AAD if supplied
if (!EVP_DecryptUpdate(context, NULL, &temp_len, aad, aad_len)) {
goto AES_ERROR;
}
if (!EVP_DecryptUpdate(context, dst, &temp_len, src, src_len)) {
goto AES_ERROR;
}
output_len = temp_len;

// Set expected tag value.
if(!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, 16, tag)) {
goto AES_ERROR;
}
// Finalise the decryption. A positive return value indicates success,
// anything else is a failure - the plaintext is not trustworthy.
if (EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len) <= 0){
goto AES_ERROR;
}
output_len += temp_len;

EVP_CIPHER_CTX_free(context);
return output_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}


Now, for the unit test.



FYI, I am not generating an IV for the unit test because I have Base64 encoded the encrypted data to store in the unit test source, therefore, I need to use a static IV with it. I just need to test the class. Don't worry, generating a unique IV will happen at a higher level. There is no AAD at this point in the tests.



Base64 and all undefined classes are unit tested and functional, so their definitions are irrelevant to the question.



Secret key I'm using is:



const unsigned char *secret = reinterpret_cast<const unsigned char*>("Open** Sesame**");


Encrypt test (passes tests, but encrypted_data may actually be invalid data!):



const unsigned char *msg = reinterpret_cast<const unsigned char*>(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encrypted_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
const size_t msg_len = 127;
size_t ciphertext_len = 256;
unsigned char ciphertext[ciphertext_len];
memset(ciphertext, 0, sizeof(ciphertext));

uint8_t iv[8], tag[16];
memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.encrypt(msg, msg_len, ciphertext, ciphertext_len, tag);

unsigned char encrypted_data[ciphertext_len];
(void)Base64::decode(encrypted_msg.c_str(), encrypted_msg.length(), encrypted_data, ciphertext_len);

TK_assertTrue(memcmp(ciphertext, encrypted_data, bytes) == 0);
TK_assertTrue(bytes == msg_len);


Decrypt test (does not pass):



std::string decrypted_msg(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encoded_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
std::string gcm_tag("hDlT5X7CGPwoPmVZSP2ezQ==");
const size_t msg_len = 127;
uint8_t iv[8], tag[16], enc_msg[msg_len], msg[msg_len], aad[msg_len];

// decode encrypted data
Base64::decode(encoded_msg.c_str(), encoded_msg.length(), enc_msg, msg_len);
Base64::decode(gcm_tag.c_str(), gcm_tag.length(), tag, sizeof(tag));

memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.decrypt(enc_msg, msg_len, msg, msg_len, aad, msg_len, tag);
TK_assertTrue(bytes == msg_len);
TK_assertTrue(strncmp(reinterpret_cast<const char*>(msg), decrypted_msg.c_str(), bytes) == 0);


Note, on the decrypt unit test, aescipher::decrypt fails in EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len).







c++ openssl aes c++98 aes-gcm






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Jan 2 at 14:44







Cinder Biscuits

















asked Jan 2 at 2:49









Cinder BiscuitsCinder Biscuits

2,5921526




2,5921526








  • 1





    You use EVP_aes_256_gcm in EVP_EncryptInit_ex but EVP_aes_128_gcm in EVP_DecryptInit_ex

    – Marek Klein
    Jan 2 at 11:30











  • Great catch! Thank you. I have updated the code to use EVP_aes_128_gcm for both encrypt and decrypt but decrypt is still failing.

    – Cinder Biscuits
    Jan 2 at 12:02











  • Can you share your secret key? I happened to have a very similar test so I can cross check your test vectors.

    – Marek Klein
    Jan 2 at 13:12






  • 1





    First try to make your secret key to be 16 bytes long. It is 12 bytes but AES 128 uses 16 bytes long keys so I suspect that you use 4 "random" bytes in tour key.

    – Marek Klein
    Jan 2 at 13:47






  • 1





    Grate to hear that. You may find this useful in future.

    – Marek Klein
    Jan 2 at 14:47
















  • 1





    You use EVP_aes_256_gcm in EVP_EncryptInit_ex but EVP_aes_128_gcm in EVP_DecryptInit_ex

    – Marek Klein
    Jan 2 at 11:30











  • Great catch! Thank you. I have updated the code to use EVP_aes_128_gcm for both encrypt and decrypt but decrypt is still failing.

    – Cinder Biscuits
    Jan 2 at 12:02











  • Can you share your secret key? I happened to have a very similar test so I can cross check your test vectors.

    – Marek Klein
    Jan 2 at 13:12






  • 1





    First try to make your secret key to be 16 bytes long. It is 12 bytes but AES 128 uses 16 bytes long keys so I suspect that you use 4 "random" bytes in tour key.

    – Marek Klein
    Jan 2 at 13:47






  • 1





    Grate to hear that. You may find this useful in future.

    – Marek Klein
    Jan 2 at 14:47










1




1





You use EVP_aes_256_gcm in EVP_EncryptInit_ex but EVP_aes_128_gcm in EVP_DecryptInit_ex

– Marek Klein
Jan 2 at 11:30





You use EVP_aes_256_gcm in EVP_EncryptInit_ex but EVP_aes_128_gcm in EVP_DecryptInit_ex

– Marek Klein
Jan 2 at 11:30













Great catch! Thank you. I have updated the code to use EVP_aes_128_gcm for both encrypt and decrypt but decrypt is still failing.

– Cinder Biscuits
Jan 2 at 12:02





Great catch! Thank you. I have updated the code to use EVP_aes_128_gcm for both encrypt and decrypt but decrypt is still failing.

– Cinder Biscuits
Jan 2 at 12:02













Can you share your secret key? I happened to have a very similar test so I can cross check your test vectors.

– Marek Klein
Jan 2 at 13:12





Can you share your secret key? I happened to have a very similar test so I can cross check your test vectors.

– Marek Klein
Jan 2 at 13:12




1




1





First try to make your secret key to be 16 bytes long. It is 12 bytes but AES 128 uses 16 bytes long keys so I suspect that you use 4 "random" bytes in tour key.

– Marek Klein
Jan 2 at 13:47





First try to make your secret key to be 16 bytes long. It is 12 bytes but AES 128 uses 16 bytes long keys so I suspect that you use 4 "random" bytes in tour key.

– Marek Klein
Jan 2 at 13:47




1




1





Grate to hear that. You may find this useful in future.

– Marek Klein
Jan 2 at 14:47







Grate to hear that. You may find this useful in future.

– Marek Klein
Jan 2 at 14:47














0






active

oldest

votes











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54000689%2fencrypt-decrypt-with-aes-gcm-using-openssl%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























0






active

oldest

votes








0






active

oldest

votes









active

oldest

votes






active

oldest

votes
















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54000689%2fencrypt-decrypt-with-aes-gcm-using-openssl%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

'app-layout' is not a known element: how to share Component with different Modules

android studio warns about leanback feature tag usage required on manifest while using Unity exported app?

WPF add header to Image with URL pettitions [duplicate]