Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security / cryptography

RSA Key Pair via OpenSSL

5.00/5 (6 votes)
19 Feb 2022CPOL1 min read 20.9K   429  
A simple C++ program to generate RSA key pair
C++ program to use OpenSSL lib to generate RSA key pair and use them for encryption/decryption

 

(Tested on Linux Mint20)

Introduction

The attached code can be used to generate RSA keys pairs, this key pair is used to encrypt plain text. This sample is intended to help OpenSSL library users.

Background

RSA key pairs are used for asymmetric ciphering. Usually, we generate a symmetric cypher key and use AES256 to encrypt the data using this key. This cypher key is encrypted using RSA public key. This is a one way function (you cannot decrypt using the public key, you will need the private key to decrypt). We share the public key only with the peer to communicate securely.

Most of the content in this article is sourced from Google searches and StackOverflow.

Using the Code

As with my previous articles , the code must be referred to at all times. Also, I use Qtcreator as my IDE and qmake.

The provided code is tested only on Linux Mint 20.

Let's generate our key-pair:

C++
pair<EVP_PKEY*,EVP_PKEY*> GetKeyRSApair()
{
    auto bne = BN_new();         //refer to https://www.openssl.org/docs/man1.0.2/man3/bn.html
    auto ret = BN_set_word(bne, RSA_F4);

    int bits = 2048;
    RSA *r = RSA_new();
    RSA_generate_key_ex(r, bits, bne, NULL);  //here we generate the RSA keys

    //we use a memory BIO to store the keys
    BIO *bp_public  = BIO_new(BIO_s_mem());PEM_write_bio_RSAPublicKey (bp_public, r);
    BIO *bp_private = BIO_new(BIO_s_mem());
    PEM_write_bio_RSAPrivateKey(bp_private, r, NULL, NULL, 0, NULL, NULL);

    auto pri_len = BIO_pending(bp_private);   //once the data is written to a 
                                              //memory/file BIO, we get the size
    auto pub_len = BIO_pending(bp_public);
    char *pri_key = (char*) malloc(pri_len + 1);
    char *pub_key = (char*) malloc(pub_len + 1);

    BIO_read(bp_private, pri_key, pri_len);   //now we read the BIO into a buffer
    BIO_read(bp_public, pub_key, pub_len);

    pri_key[pri_len] = '\0';
    pub_key[pub_len] = '\0';

    //printf("\n%s\n:\n%s\n", pri_key, pub_key);fflush(stdout);  //now we print the keys 
    //to stdout (DO NOT PRINT private key in production code, this has to be a secret)

    BIO *pbkeybio = NULL;
    pbkeybio=BIO_new_mem_buf((void*) pub_key, pub_len);  //we create a buffer BIO 
                                     //(this is different from the memory BIO created earlier)
    BIO *prkeybio = NULL;
    prkeybio=BIO_new_mem_buf((void*) pri_key, pri_len);

    RSA *pb_rsa = NULL;
    RSA *p_rsa = NULL;

    pb_rsa = PEM_read_bio_RSAPublicKey(pbkeybio, &pb_rsa, NULL, NULL);  //now we read the 
                                                                   //BIO to get the RSA key
    p_rsa = PEM_read_bio_RSAPrivateKey(prkeybio, &p_rsa, NULL, NULL);

    EVP_PKEY *evp_pbkey = EVP_PKEY_new();  //we want EVP keys , openssl libraries 
                         //work best with this type, https://wiki.openssl.org/index.php/EVP
    EVP_PKEY_assign_RSA(evp_pbkey, pb_rsa);

    EVP_PKEY *evp_prkey = EVP_PKEY_new();
    EVP_PKEY_assign_RSA(evp_prkey, p_rsa);

    //clean up
    free(pri_key);free(pub_key);
    BIO_free_all(bp_public);BIO_free_all(bp_private);
    BIO_free(pbkeybio);BIO_free(prkeybio);
    BN_free(bne);
    RSA_free(r);

    return {evp_pbkey,evp_prkey};
}

Remember to free the keys once you are done.

C++
EVP_PKEY_free(keypair.first);
EVP_PKEY_free(keypair.second);

Let's encrypt/decrypt

This code comes straight from https://wiki.openssl.org/index.php/EVP_Asymmetric_Encryption_and_Decryption_of_an_Envelope

C++
vector<unsigned char> envelope_seal
      (EVP_PKEY **pub_key, unsigned char *plaintext, int plaintext_len,
    unsigned char **encrypted_key, int *encrypted_key_len, unsigned char *iv)
{
    EVP_CIPHER_CTX *ctx;
    int ciphertext_len;
    int len;

    /* Create and initialise the context */
    ctx = EVP_CIPHER_CTX_new();

    /* Initialise the envelope seal operation. This operation generates
     * a key for the provided cipher, and then encrypts that key a number
     * of times (one for each public key provided in the pub_key array). In
     * this example the array size is just one. This operation also
     * generates an IV and places it in iv. */
    EVP_SealInit(ctx, EVP_aes_256_cbc(), encrypted_key,encrypted_key_len, iv, pub_key, 1);

    int blocksize=EVP_CIPHER_CTX_block_size(ctx);
    /* Provide the message to be encrypted, and obtain the encrypted output.
     * EVP_SealUpdate can be called multiple times if necessary
     */
    vector<unsigned char> cyphered(plaintext_len+blocksize-1);//https://www.openssl.org/docs/man1.1.1/man3/EVP_EncryptInit.html  
    //The amount of data written depends on the block alignment of the encrypted data. For most ciphers and modes, the amount of data written can be anything from zero bytes to (inl + cipher_block_size - 1) bytes.

    len=cyphered.size();
    EVP_SealUpdate(ctx, &cyphered[0], &len, plaintext, plaintext_len);
    ciphertext_len = len;

    /* Finalise the encryption. Further ciphertext bytes may be written at
     * this stage.
     */
    EVP_SealFinal(ctx, &cyphered[0] + len, &len);
    ciphertext_len += len;

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);
    cyphered.resize(ciphertext_len);
    return cyphered;
}

vector<unsigned char> envelope_open(EVP_PKEY *priv_key, unsigned char *ciphertext, 
 int ciphertext_len,unsigned char *encrypted_key, int encrypted_key_len, unsigned char *iv)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;

    /* Create and initialise the context */
    ctx = EVP_CIPHER_CTX_new();

    /* Initialise the decryption operation. The asymmetric private key is
     * provided and priv_key, whilst the encrypted session key is held in
     * encrypted_key */
    EVP_OpenInit(ctx, EVP_aes_256_cbc(), encrypted_key,encrypted_key_len, iv, priv_key);

    vector<unsigned char> plaintext(ciphertext_len);
    /* Provide the message to be decrypted, and obtain the plaintext output.
     * EVP_OpenUpdate can be called multiple times if necessary
     */
    EVP_OpenUpdate(ctx, &plaintext[0], &len, ciphertext, ciphertext_len);
    plaintext_len = len;

    /* Finalise the decryption. Further plaintext bytes may be written at
     * this stage.
     */
    EVP_OpenFinal(ctx, &plaintext[0] + len, &len);
    plaintext_len += len;

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);
    plaintext.resize(plaintext_len);
    return plaintext;
}

Now to call the functions:

This is the best part:

C++
auto keypair=GetKeyRSApair();

unsigned char str[]=
    "I am encrypted4332048230948-2308402934702384-2384092384-0234-20384-2384-2384-234";

unsigned char iv[EVP_MAX_IV_LENGTH]={};
unsigned char *encrypted_key=(unsigned char*)malloc(EVP_PKEY_size(keypair.first)); 
                     //https://www.openssl.org/docs/man1.1.1/man3/EVP_SealInit.html
int encrypted_key_len=EVP_PKEY_size(keypair.first);

vector<unsigned char> cyphered=envelope_seal(&keypair.first,str,strlen((char*)str),
                               &encrypted_key,&encrypted_key_len,iv);
string cypheredString=GetHex(cyphered);
printf("%s\n",cypheredString.c_str());

vector<unsigned char> cypheredbinary=GetBinary(cypheredString);
vector<unsigned char> plaintext = envelope_open(keypair.second,&cypheredbinary[0],
                           cypheredbinary.size(),encrypted_key,encrypted_key_len,iv);
printf("orgin text:%s:End\n",str);
printf("plain text:");
for(char c:plaintext)
    printf("%c",c);

printf(":End\n");

free(encrypted_key);
EVP_PKEY_free(keypair.first);EVP_PKEY_free(keypair.second);

History

  • 19th February, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)