Reading and writing rsa keys to a pem file in C

36,928

Solution 1

RSA keys are symmetrical, you can use any of them as a private key or public key, it is just a matter of your choice (but DSA keys are NOT symmetrical). The program below generates two 2048 bits long RSA keys, then it saves them to files and reads them back into memory. That should give you the idea how to do it.

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>

const char* pcszPassphrase = "open sezamee";

static void gen_callback(int iWhat, int inPrime, void* pParam);
static void init_openssl(void);
static void cleanup_openssl(void);
static int passwd_callback(char *pcszBuff,int size,int rwflag, void *pPass);
static EVP_PKEY* create_rsa_key(void);
static void handle_openssl_error(void);

int main(int argc, char **argv)
{
    int iRet = EXIT_SUCCESS;
    EVP_PKEY* pPrivKey = NULL;
    EVP_PKEY* pPubKey  = NULL;
    FILE*     pFile    = NULL;
    const EVP_CIPHER* pCipher = NULL;
    init_openssl();

    pPrivKey = create_rsa_key();
    pPubKey  = create_rsa_key();

    if(pPrivKey && pPubKey)
    {/* Save the keys */
        if((pFile = fopen("privkey.pem","wt")) && (pCipher = EVP_aes_256_cbc()))
        {

            if(!PEM_write_PrivateKey(pFile,pPrivKey,pCipher,
                                    (unsigned char*)pcszPassphrase,
                                    (int)strlen(pcszPassphrase),NULL,NULL))
            {
                fprintf(stderr,"PEM_write_PrivateKey failed.\n");
                handle_openssl_error();
                iRet = EXIT_FAILURE;
            }
            fclose(pFile);
            pFile = NULL;
            if(iRet == EXIT_SUCCESS)
            {
                if((pFile = fopen("pubkey.pem","wt")) && PEM_write_PUBKEY(pFile,pPubKey))
                    fprintf(stderr,"Both keys saved.\n");
                else
                {
                    handle_openssl_error();
                    iRet = EXIT_FAILURE;
                }
                if(pFile)
                {
                    fclose(pFile);
                    pFile = NULL;
                }
            }
        }
        else
        {
            fprintf(stderr,"Cannot create \"privkey.pem\".\n");
            handle_openssl_error();
            iRet = EXIT_FAILURE;
            if(pFile)
            {
                fclose(pFile);
                pFile = NULL;
            }
        }
        if(iRet == EXIT_SUCCESS)
        {/* Read the keys */
            EVP_PKEY_free(pPrivKey);
            pPrivKey = NULL;
            EVP_PKEY_free(pPubKey);
            pPubKey = NULL;

            if((pFile = fopen("privkey.pem","rt")) && 
               (pPrivKey = PEM_read_PrivateKey(pFile,NULL,passwd_callback,(void*)pcszPassphrase)))
            {
                fprintf(stderr,"Private key read.\n");
            }
            else
            {
                fprintf(stderr,"Cannot read \"privkey.pem\".\n");
                handle_openssl_error();
                iRet = EXIT_FAILURE;
            }
            if(pFile)
            {
                fclose(pFile);
                pFile = NULL;
            }

            if((pFile = fopen("pubkey.pem","rt")) && 
               (pPubKey = PEM_read_PUBKEY(pFile,NULL,NULL,NULL)))
            {
                fprintf(stderr,"Public key read.\n");
            }
            else
            {
                fprintf(stderr,"Cannot read \"pubkey.pem\".\n");
                handle_openssl_error();
                iRet = EXIT_FAILURE;
            }
        }
    }

    if(pPrivKey)
    {
        EVP_PKEY_free(pPrivKey);
        pPrivKey = NULL;
    }
    if(pPubKey)
    {
        EVP_PKEY_free(pPubKey);
        pPubKey = NULL;
    }
    cleanup_openssl();
    return iRet;
}

EVP_PKEY* create_rsa_key(void)
{
    RSA *pRSA      = NULL;
    EVP_PKEY* pKey = NULL;
    pRSA = RSA_generate_key(2048,RSA_3,gen_callback,NULL);
    pKey = EVP_PKEY_new();
    if(pRSA && pKey && EVP_PKEY_assign_RSA(pKey,pRSA))
    {
        /* pKey owns pRSA from now */
        if(RSA_check_key(pRSA) <= 0)
        {
            fprintf(stderr,"RSA_check_key failed.\n");
            handle_openssl_error();
            EVP_PKEY_free(pKey);
            pKey = NULL;
        }
    }
    else
    {
        handle_openssl_error();
        if(pRSA)
        {
            RSA_free(pRSA);
            pRSA = NULL;
        }
        if(pKey)
        {
            EVP_PKEY_free(pKey);
            pKey = NULL;
        }
    }
    return pKey;
}

void gen_callback(int iWhat, int inPrime, void* pParam)
{
    char c='*';
    switch(iWhat)
    {
        case 0: c = '.';  break;
        case 1: c = '+';  break;
        case 2: c = '*';  break;
        case 3: c = '\n'; break;
    }
    fprintf(stderr,"%c",c);
}

int passwd_callback(char *pcszBuff,int size,int rwflag, void *pPass)
{
    size_t unPass = strlen((char*)pPass);
    if(unPass > (size_t)size)
        unPass = (size_t)size;
    memcpy(pcszBuff, pPass, unPass);
    return (int)unPass;
}

void init_openssl(void)
{
    if(SSL_library_init())
    {
        SSL_load_error_strings();
        OpenSSL_add_all_algorithms();
        RAND_load_file("/dev/urandom", 1024);
    }
    else
        exit(EXIT_FAILURE);
}

void cleanup_openssl(void)
{
    CRYPTO_cleanup_all_ex_data();
    ERR_free_strings();
    ERR_remove_thread_state(0);
    EVP_cleanup();
}

void handle_openssl_error(void)
{
    ERR_print_errors_fp(stderr);
}

Solution 2

The answer given by sirgeorge has a mistake in the Code. The create_rsa_key should not be called twice. If it is called twice then private key does not have matching public key. This results in problems during decryption. Modifications needed in main method

RSA *pRSA      = NULL;
pRSA = RSA_generate_key(2048,RSA_3,gen_callback,NULL);    
pPrivKey = create_rsa_key(pRSA);
pPubKey  = create_rsa_key(pRSA);

Modifications needed in create_rsa_key

EVP_PKEY* create_rsa_key(RSA *pRSA)
{
    EVP_PKEY* pKey = NULL;
    pKey = EVP_PKEY_new();
    if(pRSA && pKey && EVP_PKEY_assign_RSA(pKey,pRSA))
    {
        /* pKey owns pRSA from now */
        if(RSA_check_key(pRSA) <= 0)
        {
            fprintf(stderr,"RSA_check_key failed.\n");
            handle_openssl_error();
            EVP_PKEY_free(pKey);
            pKey = NULL;
        }
    }
    else
    {
        handle_openssl_error();
        if(pRSA)
        {
            RSA_free(pRSA);
            pRSA = NULL;
        }
        if(pKey)
        {
            EVP_PKEY_free(pKey);
            pKey = NULL;
        }
    }
    return pKey;
}
Share:
36,928
Himanshu Jindal
Author by

Himanshu Jindal

Updated on August 08, 2020

Comments

  • Himanshu Jindal
    Himanshu Jindal over 3 years

    I am writing a C program to generate keys for RSA and write them to a file and then read from them. The homework requires me to generate the files in a openssl format. So, I chose PEM. Now, I have the following function for creating the file

    rsa = RSA_new();
    // These 3 keys are generated beforehand
    rsa->e = e;
    rsa->n = n;
    rsa->d = d;
    
    fp = fopen(pubkey_file, "w");
    if(!PEM_write_RSAPublicKey(fp, rsa))
    {
        printf("\n%s\n", "Error writing public key");
    }
    fflush(fp);
    fclose(fp);
    
    fp = fopen(privkey_file, "w");
    // pRsaKey = EVP_PKEY_new();
    // EVP_PKEY_assign_RSA(pRsaKey, rsa);
    if(!PEM_write_RSAPrivateKey(fp, rsa, NULL, 0, 0, NULL, NULL))
    // if (!PEM_write_PrivateKey(fp, pRsaKey, NULL, NULL, 0, 0, NULL))
    {
        printf("\n%s\n", "Error writing private key");
    }
    fflush(fp);
    fclose(fp);
    

    And this is the function to read the files

    rsa = RSA_new();
    fp = fopen(pubkey_file, "r");
    if(PEM_read_RSAPublicKey(fp, &rsa, NULL, NULL) == NULL)
    {
        printf("\n%s\n", "Error Reading public key");
        return;
    }
    
    fclose(fp);
    BN_bn2bin(rsa->n, (unsigned char *)modulus);
    BN_bn2bin(rsa->e, (unsigned char *)exp);
    printf("\n%s\n%s\n", exp, modulus);
    RSA_free(rsa);
    
    // pRsaKey = EVP_PKEY_new();
    fp = fopen(privkey_file, "r");
    if(fp)
        // if((PEM_read_PrivateKey(fp, &pRsaKey, NULL, NULL)) == NULL)
        if((PEM_read_RSAPrivateKey(fp, &rsa, NULL, NULL)) == NULL)
        {
            printf("\n%s\n", "Error Reading private key");
            return;
        }
    // rsa = RSA_new();
    // rsa = EVP_PKEY_get1_RSA(pRsaKey);
    fclose(fp);
    

    The public key is written and read as required, but the provate key fails. I have tried writing using both the rsa and the evp(which is commented in the above code). But, both fail. I cannot get my head around why this is happening or try and find where to look to debug this issue. Can anyone please provide some pointers for this?

    • Lucas Holt
      Lucas Holt over 11 years
      I would look into the possibility there are functions to pull out errors so you can find out what is happening here. Also check fp after you call fopen and print any errors there. As is, it's impossible to debug.
    • WhozCraig
      WhozCraig over 11 years
      I'm not familiar with the toolkit you're using, but PKCS#1 requires more (last I checked anyway) than just e n and d. All PEM is really doing is taking the PKCS#1 DER encoding, b64-encoding, and tagging some headers and footers in. Is it possible the key data is not fully fleshed before the save?
    • Himanshu Jindal
      Himanshu Jindal over 11 years
      @LucasHolt I checked the file pointers. They do not have any errors. However, it is only the function call to read private key which leads to errors.
    • Himanshu Jindal
      Himanshu Jindal over 11 years
      @WhozCraig I will look into it. But, as far as I know, RSA object only requires n, e, d. Rest can be null. openssl.org/docs/crypto/rsa.html#DESCRIPTION Hence, my assumptions that it should write the object to a file (which it does). Its reading it back, which is an issue
  • Nitesh Meshram
    Nitesh Meshram about 11 years
    HI @Sirgeorge I have added this code in my application but I am getting error : " #include <openssl/bio.h> file not found" how to resole this error. Thanks in advance...
  • sirgeorge
    sirgeorge about 11 years
    @Nitesh Meshram : Do you have openssl istalled ? I guess you don't.
  • Nitesh Meshram
    Nitesh Meshram about 11 years
    - No I have downloaded openssl and added that in my Xcode project ? Is it fine or what do you mean by installation ??
  • jww
    jww about 9 years
    Good catch. I'm surprised no one commented on that in all this time.
  • user2281107
    user2281107 about 9 years
    Thanks jww.. Actually I was using this code and stumbled upon this issue
  • Rndp13
    Rndp13 almost 8 years
    Please edit and remove the extra 'e' in "eEVP_PKEY* pKey = NULL;"
  • sirgeorge
    sirgeorge over 6 years
    @Rndp13 : fixed
  • justin
    justin over 3 years
    Note per the other answer below that the 2 RSA keys generated here are not matching each other -- RSA_generate_key should not be called twice.