Using the library to encrypt a RSA private key compatible with Web Crypto API (PBKDF2)

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Using the library to encrypt a RSA private key compatible with Web Crypto API (PBKDF2)

Claude Robitaille
This is NOT about using the command line utility, it is about using the library in a program and exporting a private key, while encrypting it.

But first; I successfully tested the resulting key using the command-line and parameters iter 64000, hmacWithSHA512 and aes256. So I know for sure (at least I think so) that openssl 1.1.1 (I am using 1.1.1d as the development library) generates something compatible with the Web Crypt API importKey function (the parameters are the same as default in openCrypto, which is a wrapper around Web Crypto API).

Now, for my application. I am not sure how to do this. I normally use PEM_write_bio_PKCS8PrivateKey to generate a PEM string. That function do accept a passphrase and a cipher but it is not possible to set parameters. The default values, as I could gather by reading on the Internet are weak so I just do not want to lower the Web Crypto API end. How to proceed? Is there another function that can be used?

How does the command line utility do?

I looked at the EVP_KDF but soon realized it was not 1.1.1 but only in the new 3.0 beta stuff, which I prefer not to use now.



Reply | Threaded
Open this post in threaded view
|

Re: Using the library to encrypt a RSA private key compatible with Web Crypto API (PBKDF2)

Viktor Dukhovni
On Wed, Jul 22, 2020 at 08:36:30PM +0000, Claude Robitaille wrote:

> This is NOT about using the command line utility, it is about using
> the library in a program and exporting a private key, while encrypting
> it.

Encrypted public keys are created via the PKCS8 API.

> Now, for my application. I am not sure how to do this. I normally use
> PEM_write_bio_PKCS8PrivateKey to generate a PEM string. That function
> do accept a passphrase and a cipher but it is not possible to set
> parameters. The default values, as I could gather by reading on the
> Internet are weak so I just do not want to lower the Web Crypto API
> end. How to proceed? Is there another function that can be used?

The function you're looking for is PEM_write_bio_PKCS8_PRIV_KEY_INFO().
With error checks elided, it boils down to:

    1. const EVP_CIPHER *cipher = EVP_get_cipherbyname(SN_aes_256_cbc);
    2. X509_ALGOR *pbe = PKCS5_pbe2_set_iv(cipher, iter, NULL, 0, NULL, NID_hmacWithSHA512);

        The function in question needs documentation, it would be fanstastic
        if someone stepped up to document it, or at least open an issue
        asking for same:

            X509_ALGOR *PKCS5_pbe2_set_iv(const EVP_CIPHER *cipher, int iter,
                                          unsigned char *salt, int saltlen,
                                          unsigned char *aiv, int prf_nid);

        When the IV is null a random IV is generated.
        When the salt is NULL and saltlen == 0 a random salt is used.
        /* When saltlen is 0 and salt is not NULL you may get a segfault! */

        The return value is freed with: void X509_ALGOR_free(X509_ALGOR *);

    3.  PKCS8_PRIV_KEY_INFO *p8inf =  EVP_PKEY2PKCS8(pkey);
    4.  X509_SIG *p8 = PKCS8_set0_pbe(pass, strlen(pass), p8inf, pbe);
         
         Here, you might want to pay attention to the character set in
         which the password is encoded, if it is not all ASCII, it
         should be UTF-8 (https://tools.ietf.org/html/rfc8018)

    5.   BIO *bio_out = ...
    6.   ret = PEM_write_bio_PKCS8(out_bio, p8);

--
    Viktor.
Reply | Threaded
Open this post in threaded view
|

Re: Using the library to encrypt a RSA private key compatible with Web Crypto API (PBKDF2)

Claude Robitaille
Super, that works beautifully. Thanks!!

Now, for sake of completeness, if I wanted to do the opposite, i.e. decrypt a key, I guess the steps are the same, using PEM_read_bio_PKCS8 at the end. Except that the salt and IV must be extracted from the PEM string. What is the function to do that?

From: openssl-users <[hidden email]> on behalf of Viktor Dukhovni <[hidden email]>
Sent: July 22, 2020 7:17 PM
To: [hidden email] <[hidden email]>
Subject: Re: Using the library to encrypt a RSA private key compatible with Web Crypto API (PBKDF2)
 
On Wed, Jul 22, 2020 at 08:36:30PM +0000, Claude Robitaille wrote:

> This is NOT about using the command line utility, it is about using
> the library in a program and exporting a private key, while encrypting
> it.

Encrypted public keys are created via the PKCS8 API.

> Now, for my application. I am not sure how to do this. I normally use
> PEM_write_bio_PKCS8PrivateKey to generate a PEM string. That function
> do accept a passphrase and a cipher but it is not possible to set
> parameters. The default values, as I could gather by reading on the
> Internet are weak so I just do not want to lower the Web Crypto API
> end. How to proceed? Is there another function that can be used?

The function you're looking for is PEM_write_bio_PKCS8_PRIV_KEY_INFO().
With error checks elided, it boils down to:

    1. const EVP_CIPHER *cipher = EVP_get_cipherbyname(SN_aes_256_cbc);
    2. X509_ALGOR *pbe = PKCS5_pbe2_set_iv(cipher, iter, NULL, 0, NULL, NID_hmacWithSHA512);

        The function in question needs documentation, it would be fanstastic
        if someone stepped up to document it, or at least open an issue
        asking for same:

            X509_ALGOR *PKCS5_pbe2_set_iv(const EVP_CIPHER *cipher, int iter,
                                          unsigned char *salt, int saltlen,
                                          unsigned char *aiv, int prf_nid);

        When the IV is null a random IV is generated.
        When the salt is NULL and saltlen == 0 a random salt is used.
        /* When saltlen is 0 and salt is not NULL you may get a segfault! */

        The return value is freed with: void X509_ALGOR_free(X509_ALGOR *);

    3.  PKCS8_PRIV_KEY_INFO *p8inf =  EVP_PKEY2PKCS8(pkey);
    4.  X509_SIG *p8 = PKCS8_set0_pbe(pass, strlen(pass), p8inf, pbe);
        
         Here, you might want to pay attention to the character set in
         which the password is encoded, if it is not all ASCII, it
         should be UTF-8 (https://eur06.safelinks.protection.outlook.com/?url=https%3A%2F%2Ftools.ietf.org%2Fhtml%2Frfc8018&amp;data=02%7C01%7C%7C6ecb294783f64bd662eb08d82e969dbe%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C637310571707466330&amp;sdata=QJ0dIUR%2FOsrgZn2SpnoRLHQRNZ1E7T4sB0O1CJyWIK8%3D&amp;reserved=0)

    5.   BIO *bio_out = ...
    6.   ret = PEM_write_bio_PKCS8(out_bio, p8);

--
    Viktor.
Reply | Threaded
Open this post in threaded view
|

Re: Using the library to encrypt a RSA private key compatible with Web Crypto API (PBKDF2)

Viktor Dukhovni
On Thu, Jul 23, 2020 at 03:04:30PM +0000, Claude Robitaille wrote:

> Now, for sake of completeness, if I wanted to do the opposite, i.e.
> decrypt a key, I guess the steps are the same, using
> PEM_read_bio_PKCS8 at the end. Except that the salt and IV must be
> extracted from the PEM string. What is the function to do that?

Reading is much simpler:

    EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x,
                                      pem_password_cb *cb, void *u);

Just set pass = "sesame" and call:

    EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio_in, NULL, NULL, pass);

It is possible to use the PKCS8 routines explicitly with:

    PKCS8 *PEM_read_bio_PKCS8(BIO *bp, TYPE **a, pem_password_cb *cb, void *u);

as follows:

    X509_SIG *p8 = PEM_read_bio_PKCS8(bio_in, NULL, NULL, NULL);
    PKCS8_PRIV_KEY_INFO *p8inf = PKCS8_decrypt(p8, pass, strlen(pass));
    EVP_PKEY *pkey = EVP_PKCS82PKEY(p8inf);

but there's little reason to do that.  The PBE algorithm, salt and IV
are handled internally.

--
    Viktor.