Search code examples
copensslaesrsalibcrypto

Decrypting an AES-encrypted message with an RSA-encrypted key with EVP tool


For industrial purposes, I want to decrypt an AES-encrypted message with an RSA-encrypted key in C. At first, I thought doing it step-by-step by first, using OpenSSL libcrypto library, by first RSA decoding the key then AES decoding the data.

I have found out that EVP tools were commonly seen as a better way to do this since it actually does what the low-levels functions do but correctly. Here is what I see the flow of the program :

  • Initialize OpenSSL;
  • Read and store the RSA private key;
  • Initialize the decryption by specifying the decryption algorithm (AES) and the private key;
  • Update the decryption by giving the key, the data, the key and their length
  • Finally decrypt the data and return it.

I have been a lot confused by the fact that so far we do not intend to use any IV or ADD (although IV might come up later in the project). I have followed this guide it is not very clear and does not fit the way I use EVP.

So here is my actual code :

#include <openssl/evp.h>
#include <openssl/conf.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/aes.h>
#include <openssl/err.h>
#include "openssl\applink.c" 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

const char PRIVATE_KEY_PATH[] = "C:/Users/Local_user/privateKey.pem";

EVP_PKEY* initializePrivateKey(void)
{
    FILE* privateKeyfile;
    if ((privateKeyfile = fopen(PRIVATE_KEY_PATH, "r")) == NULL) // Check PEM file opening
    {
        perror("Error while trying to access to private key.\n");
        return NULL;
    }

    RSA *rsaPrivateKey = RSA_new();
    EVP_PKEY *privateKey = EVP_PKEY_new();

    if ((rsaPrivateKey = PEM_read_RSAPrivateKey(privateKeyfile, &rsaPrivateKey, NULL, NULL)) == NULL) // Check PEM file reading
    {
        fprintf(stderr, "Error loading RSA Private Key File.\n");
        ERR_print_errors_fp(stderr);
        return NULL;
    }
    if (!EVP_PKEY_assign_RSA(privateKey, rsaPrivateKey))
    {
        fprintf(stderr, "Error when initializing EVP private key.\n");
        ERR_print_errors_fp(stderr);
        return NULL;
    }
    return privateKey;
}
const uint8_t* decodeWrappingKey(uint8_t const* data, const size_t data_len, uint8_t const* wrappingKey, const size_t wrappingKey_len)
{
    // Start Decryption
    EVP_CIPHER_CTX *ctx;
    if (!(ctx = EVP_CIPHER_CTX_new())) // Initialize context
    {
        fprintf(stderr, "Error when initializing context.\n");
        ERR_print_errors_fp(stderr);
        return NULL;
    }
    EVP_PKEY *privateKey = initializePrivateKey();
    if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, privateKey, NULL)) // Initialize decryption
    {
        fprintf(stderr, "Error when initializing decryption.\n");
        ERR_print_errors_fp(stderr);
        return NULL;
    } 
    uint8_t* res;
    if ((res = calloc(data_len, sizeof(uint8_t))) == NULL) // Check memory allocating
    {
        perror("Memory allocating error ");
        return NULL;
    }
    puts("Initialization done. Decoding..\n");
    size_t res_len = 0;
    if (1 != EVP_DecryptUpdate(ctx, res, &res_len, data, data_len))
    {
        fprintf(stderr, "Error when preparing decryption.\n");
        ERR_print_errors_fp(stderr);
    }


    if (1 != EVP_DecryptFinal_ex(ctx, res, &res_len))
    {
        fprintf(stderr, "Error when decrypting.\n");
        ERR_print_errors_fp(stderr);
    }
    return res;
}

void hexToBytes(uint8_t *des, char const *source, const size_t size) {

    for (int i = 0; i < size - 1; i += 2) 
        sscanf(source + i, "%02x", des + (i / 2));
}

int main(void) {
    char const *strWrap = "5f82c48f85054ef6a3b2621819dd0e969030c79cc00deb89........";
    char const *strData = "ca1518d44716e3a4588af741982f29ad0a3e7a8d67.....";

    uint8_t *wrap = calloc(strlen(strWrap), sizeof(uint8_t));
    hexToBytes(wrap, strWrap, strlen(strWrap)); // Converts string to raw data
    uint8_t *data = calloc(strlen(strData), sizeof(uint8_t));
    hexToBytes(data, strData, strlen(strData));

    /* Load the human readable error strings for libcrypto */
    ERR_load_crypto_strings();

    /* Load all digest and cipher algorithms */
    OpenSSL_add_all_algorithms();

    /* Load config file, and other important initialisation */
    OPENSSL_config(NULL);
    const uint8_t *res = decodeWrappingKey(data, strlen(strData) / 2, wrap, strlen(strWrap) / 2);
    if (res == NULL)
        return 1;
    return 0;
}

My output is the following one :

Initialization done. Decoding..

Error when preparing decryption.
Error when decrypting. 

Obviously it fails when updating and finalising the decryption but I can't figure out why and the ERR_print_errors_fp(stderr); which had always worked for me so far seems to be mute.


Solution

  • Here is a complete working example of how you can encrypt a key using RSA, and encrypt a message using that key using AES, followed by the subsequent decryption of those things. It assumes AES-256-CBC is being used. If you want to use AES-256-GCM instead then you will need to make some changes to get and set the tag (ask me if you need some pointers on how to do this). It also assumes that the RSA encryption is done with PKCS#1 padding (which is all that the EVP_Seal* APIs support). If you need some other kind of padding then you will need to use a different method. Finally it assumes you are using OpenSSL 1.1.0. If you are using 1.0.2 then some changes will probably be necessary (at least you will need to explicitly init and de-init the library - that isn't required in 1.1.0).

    The code reads the RSA private and public keys from files called privkey.pem and pubkey.pem which are in the current working directory. I generated these files like this:

    openssl genrsa -out privkey.pem 2048
    openssl rsa -in privkey.pem -pubout -out pubkey.pem
    

    I've tested this on Linux only. The code is as follows:

    #include <openssl/evp.h>
    #include <openssl/pem.h>
    #include <openssl/err.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    static int envelope_seal(EVP_PKEY *pub_key, unsigned char *plaintext,
                             int plaintext_len, unsigned char **encrypted_key,
                             int *encrypted_key_len, unsigned char **iv,
                             int *iv_len,  unsigned char **ciphertext,
                             int *ciphertext_len)
    {
        EVP_CIPHER_CTX *ctx;
        int len, ret = 0;
        const EVP_CIPHER *type = EVP_aes_256_cbc();
        unsigned char *tmpiv = NULL, *tmpenc_key = NULL, *tmpctxt = NULL;
    
        if((ctx = EVP_CIPHER_CTX_new()) == NULL)
            return 0;
    
        *iv_len = EVP_CIPHER_iv_length(type);
        if ((tmpiv = malloc(*iv_len)) == NULL)
            goto err;
    
        if ((tmpenc_key = malloc(EVP_PKEY_size(pub_key))) == NULL)
            goto err;
    
        if ((tmpctxt = malloc(plaintext_len + EVP_CIPHER_block_size(type)))
                == NULL)
            goto err;
    
        if(EVP_SealInit(ctx, type, &tmpenc_key, encrypted_key_len, tmpiv, &pub_key,
                        1) != 1)
            goto err;
    
        if(EVP_SealUpdate(ctx, tmpctxt, &len, plaintext, plaintext_len) != 1)
            goto err;
        *ciphertext_len = len;
    
        if(EVP_SealFinal(ctx, tmpctxt + len, &len) != 1)
            goto err;
        *ciphertext_len += len;
    
        *iv = tmpiv;
        *encrypted_key = tmpenc_key;
        *ciphertext = tmpctxt;
        tmpiv = NULL;
        tmpenc_key = NULL;
        tmpctxt = NULL;
        ret = 1;
     err:
        EVP_CIPHER_CTX_free(ctx);
        free(tmpiv);
        free(tmpenc_key);
        free(tmpctxt);
    
        return ret;
    }
    
    int envelope_open(EVP_PKEY *priv_key, unsigned char *ciphertext,
                      int ciphertext_len, unsigned char *encrypted_key,
                      int encrypted_key_len, unsigned char *iv,
                      unsigned char **plaintext, int *plaintext_len)
    {
        EVP_CIPHER_CTX *ctx;
        int len, ret = 0;
        unsigned char *tmpptxt = NULL;
    
        if((ctx = EVP_CIPHER_CTX_new()) == NULL)
            return 0;
    
        if ((tmpptxt = malloc(ciphertext_len)) == NULL)
            goto err;
    
        if(EVP_OpenInit(ctx, EVP_aes_256_cbc(), encrypted_key, encrypted_key_len,
                        iv, priv_key) != 1)
            return 0;
    
        if(EVP_OpenUpdate(ctx, tmpptxt, &len, ciphertext, ciphertext_len) != 1)
            return 0;
        *plaintext_len = len;
    
        if(EVP_OpenFinal(ctx, tmpptxt + len, &len) != 1)
            return 0;
        *plaintext_len += len;
    
        *plaintext = tmpptxt;
        tmpptxt = NULL;
        ret = 1;
     err:
        EVP_CIPHER_CTX_free(ctx);
        free(tmpptxt);
    
        return ret;
    }
    
    int main(void)
    {
        EVP_PKEY *pubkey = NULL, *privkey = NULL;
        FILE *pubkeyfile, *privkeyfile;
        int ret = 1;
        unsigned char *iv = NULL, *message = "Hello World!\n";
        unsigned char *enc_key = NULL, *ciphertext = NULL, *plaintext = NULL;
        int iv_len = 0, enc_key_len = 0, ciphertext_len = 0, plaintext_len = 0, i;
    
        if ((pubkeyfile = fopen("pubkey.pem", "r")) == NULL) {
            printf("Failed to open public key for reading\n");
            goto err;
        }
        if ((pubkey = PEM_read_PUBKEY(pubkeyfile, &pubkey, NULL, NULL)) == NULL) {
            fclose(pubkeyfile);
            goto err;
        }
        fclose(pubkeyfile);
    
        if ((privkeyfile = fopen("privkey.pem", "r")) == NULL) {
            printf("Failed to open private key for reading\n");
            goto err;
        }
        if ((privkey = PEM_read_PrivateKey(privkeyfile, &privkey, NULL, NULL))
                == NULL) {
            fclose(privkeyfile);
            goto err;
        }
        fclose(privkeyfile);
    
        if (!envelope_seal(pubkey, message, strlen(message), &enc_key, &enc_key_len,
                           &iv, &iv_len, &ciphertext, &ciphertext_len))
            goto err;
    
        printf("Ciphertext:\n");
        for (i = 0; i < ciphertext_len; i++)
            printf("%02x", ciphertext[i]);
        printf("\n");
    
        printf("Encrypted Key:\n");
        for (i = 0; i < enc_key_len; i++)
            printf("%02x", enc_key[i]);
        printf("\n");
    
        printf("IV:\n");
        for (i = 0; i < iv_len; i++)
            printf("%02x", iv[i]);
        printf("\n");
    
        if (!envelope_open(privkey, ciphertext, ciphertext_len, enc_key,
                           enc_key_len, iv, &plaintext, &plaintext_len))
            goto err;
    
        plaintext[plaintext_len] = '\0';
        printf("Plaintext: %s\n", plaintext);
    
        ret = 0;
     err:
        if (ret != 0) {
            printf("Error\n");
            ERR_print_errors_fp(stdout);
        }
        EVP_PKEY_free(pubkey);
        EVP_PKEY_free(privkey);
        free(iv);
        free(enc_key);
        free(ciphertext);
        free(plaintext);
    
        return ret;
    }