How to replace deprecated RSA low level OpenSSL 3.0 APIs with equivalent EVP functions?

Issue

This working RSA OpenSSL code

void SwapBytes( unsigned char *pv, size_t n )
{
   unsigned char *p = pv;
   size_t lo, hi;
   for ( lo = 0, hi = n - 1; hi > lo; lo++, hi-- )
   {
      char tmp = p[lo];
      p[lo] = p[hi];
      p[hi] = tmp;
   }
}

void RSA(unsigned char *plaintext, unsigned char *ciphertext)
{
        BIGNUM *bnN = NULL;
        BIGNUM *bnE = NULL;

        RSA *keys = RSA_new();

        BN_hex2bn(&bnN, modulus);
        BN_hex2bn(&bnE, public_exp);

        RSA_set0_key(keys, bnN, bnE, NULL);

        int modulus_size = RSA_size(keys);

        SwapBytes(plaintext, modulus_size);
        
        int cipher_len = RSA_public_encrypt(modulus_size, plaintext, ciphertext, keys, RSA_NO_PADDING);

        RSA_free(keys);

        SwapBytes(ciphertext, modulus_size);
}  

when compiled produces deprecated warnings such as

/mnt/c/Projects/src/rsa.cpp:37:102: warning: ‘int RSA_public_encrypt(int, const unsigned char*, unsigned char*, RSA*, int)’ is deprecated: Since OpenSSL 3.0 [-Wdeprecated-declarations]
   37 |         int cipher_len = RSA_public_encrypt(modulus_size, plaintext, ciphertext, keys, RSA_NO_PADDING);

which can be suppressed using this compiler option

-Wno-deprecated-declarations

However, the OpenSSL dev team notes:

Use of the low level APIs has been informally discouraged by the OpenSSL dev team for a long time.
However in OpenSSL 3.0 this is made more formal. All such low level APIs have been deprecated.
You may still use them in your applications, but you may start to see deprecation warnings
during compilation (dependent on compiler support for this).
Deprecated APIs may be removed from future versions of OpenSSL so you are strongly encouraged
to update your code to use the high level APIs instead.

Question

It’s suggested to replace the above low level API’s with EVP.
Is there an RSA example to replace the above code with OpenSSL EVP functions?

Solution

I’m no openssl expert, but going through the hard to read DOC’s I figured out the below conversation which from my testing generates the same output as as your function (assuming no SwapBytes is called as you don’t provide that).

It can be broken down into three parts I think.

Sample:

#include <cassert>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/param_build.h>

template<typename T, typename D>
std::unique_ptr<T, D> make_handle(T* handle, D deleter)
{
    return std::unique_ptr<T, D>{handle, deleter};
}

void newRSA(unsigned char *plaintext, unsigned char *ciphertext, int length, char const* modulus, char const* public_exp)
{
    // build BIGNUM values
    BIGNUM* num{ nullptr };
    BN_hex2bn(&num, modulus);
    assert(num != nullptr);
    auto bnN = make_handle(num, BN_free);
    num = nullptr;

    BN_hex2bn(&num, public_exp);
    assert(num != nullptr);
    auto bnE = make_handle(num, BN_free);

    // Build params to create PARAM array
    auto params_build = make_handle(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
    assert(params_build.get() != nullptr);

    auto result = OSSL_PARAM_BLD_push_BN(params_build.get(), "n", bnN.get());
    assert(result == 1);
    result = OSSL_PARAM_BLD_push_BN(params_build.get(), "e", bnE.get());
    assert(result == 1);
    result = OSSL_PARAM_BLD_push_BN(params_build.get(), "d", nullptr);
    assert(result == 1);

    // create PARAMS array
    auto params = make_handle(OSSL_PARAM_BLD_to_param(params_build.get()), OSSL_PARAM_free);

    // cleanup params build up
    params_build.reset();
    bnN.reset();
    bnE.reset();

    // Create RSA key from params
    auto ctx = make_handle(EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr), EVP_PKEY_CTX_free);
    assert(ctx.get() != nullptr);

    result = EVP_PKEY_fromdata_init(ctx.get());
    assert(result == 1);

    EVP_PKEY *key = nullptr;
    result = EVP_PKEY_fromdata(ctx.get(), &key, EVP_PKEY_KEYPAIR, params.get());
    assert(result == 1);

    auto keys = make_handle(key, EVP_PKEY_free);

    // cleanup params
    params.reset();


    // RSA_size equivalent
    int modulus_size = (EVP_PKEY_get_bits(keys.get()) + 7) / 8;
    assert(length == modulus_size);


    // setup encryption from the key generated above
    auto enc_ctx = make_handle(EVP_PKEY_CTX_new(keys.get(), nullptr), EVP_PKEY_CTX_free);
    assert(enc_ctx.get() != nullptr);

    result = EVP_PKEY_encrypt_init(enc_ctx.get());
    assert(result == 1);

    // encrypt using RSA_NO_PADDING
    result = EVP_PKEY_CTX_set_rsa_padding(enc_ctx.get(), RSA_NO_PADDING);
    assert(result == 1);

    // encrypt
    size_t outlen = length;
    result = EVP_PKEY_encrypt(enc_ctx.get(), ciphertext, &outlen, plaintext, length);
    assert(result == 1);

    assert(outlen == length);
}

Answered By – Shane Powell

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published