I have simple implementation of AES GCM, with encryption and decryption functions. During testing I obtained an error during decryption. In particular, after the encryption that seems correct, I have an error in the last step of the decryption process, since the EVP_DecryptFinal_ex function returns 0.
The class header is the following:
class AesGcm {
private:
const EVP_CIPHER* m_cipher;
EVP_CIPHER_CTX* m_ctx;
unsigned char* m_key;
int m_key_len;
int m_iv_len;
int m_block_size;
unsigned char* m_ciphertext;
unsigned char* m_plaintext;
unsigned char* m_iv;
public:
AesGcm(const unsigned char* key);
~AesGcm();
int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *aad,
int aad_len, unsigned char *&ciphertext, unsigned char* tag);
int decrypt(unsigned char* ciphertext, int ciphertext_len, unsigned char* aad,
int aad_len, unsigned char* iv, unsigned char* tag, unsigned char* &plaintext);
int handleErrorEncrypt(const char *msg);
int handleErrorDecrypt(const char *msg);
static constexpr unsigned int AES_TAG_LEN = 16;
unsigned char *getIV();
int getIVLength();
};
This is the implementation:
#include "AesGcm.h"
#include <iostream>
#include <cstring>
#include <openssl/err.h>
using namespace std;
/**
* @brief Constructor for AesGcm class
* @param key The encryption key
*/
AesGcm::AesGcm(const unsigned char* key) {
// Set the cipher to AES-128 GCM
m_cipher = EVP_aes_128_gcm();
// Get block size and IV length for the cipher
m_block_size = EVP_CIPHER_block_size(m_cipher);
m_iv_len = EVP_CIPHER_iv_length(m_cipher);
// Get key length and allocate memory for the key
m_key_len = EVP_CIPHER_key_length(m_cipher);
m_key = new unsigned char[m_key_len];
memcpy(m_key, key, m_key_len);
}
/**
* Destructor for AesGcm class
* Cleans up resources and securely clears sensitive data
*/
AesGcm::~AesGcm() {
// Cleanse and delete the key
OPENSSL_cleanse(m_key, m_key_len);
delete[] m_key;
}
/**
* Encryption function
* @param plaintext The input plaintext
* @param plaintext_len Length of the plaintext
* @param aad Additional authenticated data
* @param aad_len Length of the additional authenticated data
* @param ciphertext The output ciphertext
* @param tag The authentication tag
* @return Length of the ciphertext on success, -1 on failure
*/
int AesGcm::encrypt(unsigned char* plaintext, int plaintext_len, unsigned char* aad, int aad_len,
unsigned char*& ciphertext, unsigned char* tag) {
int len;
int ciphertext_len;
// Check for integer overflow
if (plaintext_len + m_block_size > INT_MAX) {
cerr << "AesGCM - Error during encryption: Integer overflow (file too large)" << endl;
return -1;
}
// Allocate memory for m_ciphertext buffer
m_ciphertext = new (nothrow) unsigned char[plaintext_len + m_block_size];
if (!m_ciphertext) {
cerr << "AesGCM - Error during encryption: Failed to allocate memory for m_ciphertext" << endl;
return -1;
}
// Generate IV
m_iv = new unsigned char[m_iv_len];
RAND_poll();
if (RAND_bytes(m_iv, m_iv_len) != 1) {
return handleErrorEncrypt("RAND_bytes for IV generation failed");
}
// Initialize the encryption operation.
m_ctx = EVP_CIPHER_CTX_new();
if (!m_ctx) {
return handleErrorEncrypt("EVP_CIPHER_CTX_new() failed");
}
// Initialize key and IV
if (!EVP_EncryptInit_ex(m_ctx, m_cipher, nullptr, m_key, m_iv)) {
return handleErrorEncrypt("EVP_EncryptInit_ex failed");
}
// Provide any AAD data.
if (!EVP_EncryptUpdate(m_ctx, nullptr, &len, aad, aad_len)) {
return handleErrorEncrypt("EVP_EncryptUpdate for AAD failed");
}
// Provide the message to be encrypted, and obtain the encrypted output.
if (!EVP_EncryptUpdate(m_ctx, m_ciphertext, &len, plaintext, plaintext_len)) {
return handleErrorEncrypt("EVP_EncryptUpdate for encryption failed");
}
ciphertext_len = len;
// Finalize the encryption.
if (!EVP_EncryptFinal_ex(m_ctx, m_ciphertext + len, &len)) {
return handleErrorEncrypt("EVP_EncryptFinal_ex failed");
}
ciphertext_len += len;
// Get the tag
if (!EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_GCM_GET_TAG, AES_TAG_LEN, tag)) {
return handleErrorEncrypt("EVP_CIPHER_CTX_ctrl for tag failed");
}
ciphertext = m_ciphertext;
EVP_CIPHER_CTX_free(m_ctx);
delete[] m_iv;
delete[] m_ciphertext;
return ciphertext_len;
}
/**
* Decryption function
* @param ciphertext The input ciphertext
* @param ciphertext_len Length of the ciphertext
* @param aad Additional authenticated data
* @param aad_len Length of the additional authenticated data
* @param iv The initialization vector
* @param tag The authentication tag
* @param plaintext The output plaintext
* @return Length of the plaintext on success, -1 on failure
*/
int AesGcm::decrypt(unsigned char* ciphertext, int ciphertext_len, unsigned char* aad, int aad_len,
unsigned char* iv, unsigned char* tag, unsigned char*& plaintext) {
int len;
int plaintext_len;
int ret;
// Allocate memory for m_plaintext buffer
m_plaintext = new (nothrow) unsigned char[ciphertext_len];
if (!m_plaintext) {
cerr << "AesGCM - Error during decryption: Failed to allocate memory for plaintext" << endl;
return -1;
}
// Initialize the decryption operation.
m_ctx = EVP_CIPHER_CTX_new();
if (!m_ctx) {
return handleErrorDecrypt("EVP_CIPHER_CTX_new() for decryption failed");
}
// Initialize key and IV
if (!EVP_DecryptInit_ex(m_ctx, m_cipher, nullptr, m_key, iv)) {
return handleErrorDecrypt("EVP_DecryptInit_ex failed");
}
// Provide any AAD data.
if (!EVP_DecryptUpdate(m_ctx, nullptr, &len, aad, aad_len)) {
return handleErrorDecrypt("EVP_DecryptUpdate for AAD failed");
}
// Provide the message to be decrypted, and obtain the plaintext output.
if (!EVP_DecryptUpdate(m_ctx, m_plaintext, &len, ciphertext, ciphertext_len)) {
return handleErrorDecrypt("EVP_DecryptUpdate for decryption failed");
}
plaintext_len = len;
// Set expected tag value.
if (!EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_GCM_SET_TAG, AES_TAG_LEN, tag)) {
return handleErrorDecrypt("EVP_CIPHER_CTX_ctrl for tag failed");
}
// Finalize the decryption.
ret = EVP_DecryptFinal_ex(m_ctx, m_plaintext + len, &len);
plaintext = m_plaintext;
EVP_CIPHER_CTX_free(m_ctx);
delete[] m_plaintext;
if (ret > 0) {
// Success
plaintext_len += len;
return plaintext_len;
} else {
// Verify failed
cerr << "AesGCM - Error during decryption: EVP_DecryptFinal_ex failed" << endl;
return -1;
}
}
/**
* Error handling function for encryption
* @param msg Error message
* @return -1 to indicate an error
*/
int AesGcm::handleErrorEncrypt(const char* msg) {
cerr << "AesGCM - Error during encryption: " << msg << endl;
delete[] m_iv;
delete[] m_ciphertext;
EVP_CIPHER_CTX_free(m_ctx);
return -1;
}
/**
* Error handling function for decryption
* @param msg Error message
* @return -1 to indicate an error
*/
int AesGcm::handleErrorDecrypt(const char* msg) {
cerr << "AesGCM - Error during decryption: " << msg << endl;
delete m_plaintext;
EVP_CIPHER_CTX_free(m_ctx);
return -1;
}
unsigned char *AesGcm::getIV() {
return m_iv;
}
int AesGcm::getIVLength() {
return m_iv_len;
}
This is a simple test, to verify the encryption and decryption functionalities:
void testEncryptionAndDecryption() {
// Replace the key with your own key
const unsigned char key[] = "0123456789abcdef";
AesGcm aesGcm(key);
// Test data
const char* plaintext = "Hello, this is a test!";
const int plaintext_len = strlen(plaintext);
const char* aad = "aad";
const int aad_len = strlen(aad);
cout << "Original Plaintext: " << plaintext << endl;
unsigned char* ciphertext = nullptr;
unsigned char tag[AesGcm::AES_TAG_LEN];
// Encrypt
int ciphertext_len = aesGcm.encrypt(
reinterpret_cast<unsigned char*>(const_cast<char*>(plaintext)),
plaintext_len,
(unsigned char *) aad, aad_len,
ciphertext,
tag
);
assert(ciphertext_len > 0);
cout << "Ciphertext Length: " << ciphertext_len << endl;
// Get IV for decryption
unsigned char* iv = aesGcm.getIV();
int iv_len = aesGcm.getIVLength();
cout << "IV length: " << iv_len << endl;
// Print IV for debugging
cout << "IV: ";
for (int i = 0; i < iv_len; ++i) {
cout << hex << setw(2) << setfill('0') << static_cast<int>(iv[i]) << " ";
}
cout << dec << endl;
// Decrypt
unsigned char* decryptedText = nullptr;
int decrypted_len = aesGcm.decrypt(
ciphertext, ciphertext_len,
(unsigned char *) aad, aad_len,
iv, tag,
decryptedText
);
assert(decrypted_len == plaintext_len);
cout << "Decrypted Plaintext: " << reinterpret_cast<char*>(decryptedText) << endl;
// Verify the decrypted text
assert(strcmp(plaintext, reinterpret_cast<char*>(decryptedText)) == 0);
// Clean up
delete[] ciphertext;
delete[] decryptedText;
cout << "Test Passed!" << endl;
}
The output of the test is this:
Original Plaintext: Hello, this is a test!
Ciphertext Length: 22
IV length: 12
IV: 8a e1 ac 50 2a 63 00 00 c6 a9 7b a1
AesGCM - Error during decryption: EVP_DecryptFinal_ex failed
AesGcmTest: /home/Documents/GitHub/Secure-Cloud-Storage/test/AesGcmTest.cpp:60: void testEncryptionAndDecryption(): Assertion `decrypted_len == plaintext_len' failed.
What could the problem be? I read that EVP_DecryptFinal() will return an error code if padding is enabled and the final block is not correctly formatted, but I don't understand where is the problem in this case. I tried also setting EVP_CIPHER_CTX_set_padding(m_ctx, 0) before the EVP_DecryptInit_ex, but I obtained the same error. The other steps of the decryption seems to go well, the only error is in the EVP_DecryptFinal_ex call.