Encrypt/Decrypt with AES-GCM using OpenSSL
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
|
show 3 more comments
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
1
You useEVP_aes_256_gcm
inEVP_EncryptInit_ex
butEVP_aes_128_gcm
inEVP_DecryptInit_ex
– Marek Klein
Jan 2 at 11:30
Great catch! Thank you. I have updated the code to useEVP_aes_128_gcm
for bothencrypt
anddecrypt
butdecrypt
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
|
show 3 more comments
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
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
c++ openssl aes c++98 aes-gcm
edited Jan 2 at 14:44
Cinder Biscuits
asked Jan 2 at 2:49
Cinder BiscuitsCinder Biscuits
2,5921526
2,5921526
1
You useEVP_aes_256_gcm
inEVP_EncryptInit_ex
butEVP_aes_128_gcm
inEVP_DecryptInit_ex
– Marek Klein
Jan 2 at 11:30
Great catch! Thank you. I have updated the code to useEVP_aes_128_gcm
for bothencrypt
anddecrypt
butdecrypt
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
|
show 3 more comments
1
You useEVP_aes_256_gcm
inEVP_EncryptInit_ex
butEVP_aes_128_gcm
inEVP_DecryptInit_ex
– Marek Klein
Jan 2 at 11:30
Great catch! Thank you. I have updated the code to useEVP_aes_128_gcm
for bothencrypt
anddecrypt
butdecrypt
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
|
show 3 more comments
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
1
You use
EVP_aes_256_gcm
inEVP_EncryptInit_ex
butEVP_aes_128_gcm
inEVP_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 bothencrypt
anddecrypt
butdecrypt
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