Error during decryption using Openssl EVP_DecryptFinal_ex

120 views Asked by At

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.

0

There are 0 answers