For over a decade I have been using RSA public key for encrypting aes-256-cbc key and iv, before transmission. On the receiving side, the AES key and iv are decrypted using RSA private key.
Now I am about to migrate from openssl 1.1.1 to 3.2.0 version, and I want to use newer encryption methods. Two major changes I am contemplating:
Unlike RSA public key, X25519 public key cannot be directly used for encrypting the AES key. So I created the shared secret using the X25519 key exchange, as described here. I am not sure what to do next. I am not able to find any openssl c/c++ example of how to encrypt using the shared secret. Every encrypt example I can find uses RSA key. What is the RSA_public_encrypt() equivalent for encrypting using this shared secret? Can EVP_PKEY_encrypt() be used for this?
Ok, I got it working after seeing an example in the openssl site.
The following code has very little error checking, just to keep it short. The variable skey is the shared secret already computed on each side.
On the side of Bob, I encrypted as followed:
EVP_CIPHER_CTX *cctx = EVP_CIPHER_CTX_new();
OPENSSL_assert(EVP_CipherInit_ex2(cctx,EVP_aes_256_cbc(),NULL,NULL,1,NULL) == 1);
std::vector<uint8_t> iv(EVP_CIPHER_CTX_get_iv_length(cctx));
RAND_bytes(&iv.at(0),iv.size());
OPENSSL_assert(EVP_CipherInit_ex2(cctx,NULL,skey,&iv.at(0),1,NULL) == 1);
const char* msg = "This is a secret message, no really!!";
uint8_t output[1000];
int outlen = 0, msg_len = 0;
OPENSSL_assert(EVP_CipherUpdate(cctx,output,&outlen,(uint8_t*)msg,strlen(msg)+1) == 1);
msg_len += outlen;
printf("\noutput size = %d\n",outlen);
OPENSSL_assert(EVP_CipherFinal_ex(cctx,output+outlen,&outlen) == 1);
msg_len += outlen;
printf("\ntotal size = %d\n",msg_len);
int iv_size = iv.size();
std::ofstream os("secret.enc",std::ios_base::binary|std::ios_base::trunc);
os.write((char*)&iv_size,sizeof(iv_size));
os.write((char*)&iv.at(0),iv.size());
os.write((char*)&msg_len,sizeof(msg_len));
os.write((char*)output,msg_len);
EVP_CIPHER_CTX_free(cctx);
On the side of Alice, I decrypted as follows:
uint8_t input[1000];
char output[1000];
std::ifstream is("secret.enc",std::ios_base::binary);
int iv_size, msg_len,outlen;
is.read((char*)&iv_size,sizeof(iv_size));
std::vector<uint8_t> iv(iv_size);
is.read((char*)&iv.at(0),iv.size());
is.read((char*)&msg_len,sizeof(msg_len));
std::vector<uint8_t> msg(msg_len);
is.read((char*)&msg.at(0),msg.size());
EVP_CIPHER_CTX *cctx = EVP_CIPHER_CTX_new();
OPENSSL_assert(EVP_CipherInit_ex2(cctx,EVP_aes_256_cbc(),skey,&iv.at(0),0,NULL) == 1);
OPENSSL_assert(EVP_CipherUpdate(cctx,(uint8_t*)output,&outlen,(uint8_t*)&msg.at(0),msg.size()) == 1);
OPENSSL_assert(EVP_CipherFinal_ex(cctx,(uint8_t*)output+outlen,&outlen) == 1);
printf("\nReceived data: %s\n",output);
EVP_CIPHER_CTX_free(cctx);
I was able to print the decrypted message on the receiving side. I could make it work for the aes_256_gcm too.
Based on the feedback from @Topaco, I derived a new key from the shared secret. Then I used this new key to encrypt and decrypt, worked fine. The code for that is follows:
EVP_KDF *kdf = EVP_KDF_fetch(NULL, "X963KDF", NULL);
EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);
EVP_KDF_free(kdf);
OSSL_PARAM params[4], *p = params;
*p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,(char*)SN_sha256, strlen(SN_sha256));
*p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SECRET,(char*)skey, skeylen);
*p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO,(char*)"label", (size_t)5);
*p = OSSL_PARAM_construct_end();
uint8_t newKey[30];memset(newKey,0,30);
if(EVP_KDF_derive(kctx, newKey, 30, params) <= 0)
ERR_print_errors_fp(stdout);
EVP_KDF_CTX_free(kctx);