I am relatively new to C and I am trying to encrypt a string data in C.
I have been provided with a PUBLIC KEY that is somewhat like
const char *pub_key_str = "-----BEGIN PUBLIC KEY-----"
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1WRkqlsZbmaNWbOZr/M4"
"ddwXKjmHUJ8zn9hy0WE15A8T4xjNuzJ/ATKbz20/2wVgFEGTWPNCUjkUvFzr3mhN"
"v/YJXo5iXQDP3M6rVv/w3SXaiR9C8yhDxWUz7WX/clmj7vDvk5Iydgq07gVScx+"
"5DBVbGAx10jHSDSa7f/NXMn9yNHad4hN0i1l+tsTB39CFsNeVFr349y4R8HA5Zma"
"EKseLc8iKzkwEQcuyMn4znaFpnOL0CmSrYB5K1E9zmmtDhMvDs540ZotcH/xpJiV"
"XEdQOWGFN6rryeAKP8y+sMRfQLxoiTg37YfsxTscf0gzKBizoamTF3TjRUNM55aE"
"BwIDAQAB"
"-----END PUBLIC KEY-----";
And a sample representation of what I am trying to achieve in Typescript
rsaEncrypt(text: string, publicKey: string): Promise<string> {
const buffer = Buffer.from(JSON.stringify(text));
const encrypted = publicEncrypt(
{
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha1',
},
buffer,
);
return encrypted.toString('base64');
}
This is my implementation however, my rsa is always NULL after PEM_read_bio_RSA_PUBKEY
int rsa_encrypt(const unsigned char *msg, size_t msg_len, unsigned char **enc_msg, size_t *enc_msg_len) {
RSA *rsa = NULL;
BIO *bio = NULL;
int ret = -1;
tappa_print("%s", "In rsa_enc");
tappa_print("%s", PUBLIC_KEY);
bio = BIO_new_mem_buf(PUBLIC_KEY, -1);
tappa_printf("Bio %d", BIO_get_init(bio));
if (bio == NULL) {
fprintf(stderr, "Error creating bio\n");
return -1;
}
rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
tappa_printf("RSA %d", rsa==NULL);
if (rsa == NULL) {
ERR_print_errors_fp(stderr);
tappa_print("%s", strerror(errno));
fprintf(stderr, "Error reading public key\n");
//BIO_free(bio);
return -1;
}
tappa_printf("RSA %d", RSA_size(rsa));
*enc_msg = (unsigned char *)malloc(RSA_size(rsa));
tappa_printf("Enc msg %d", sizeof *enc_msg);
if (*enc_msg == NULL) {
fprintf(stderr, "Memory allocation error\n");
RSA_free(rsa);
BIO_free(bio);
return -1;
}
*enc_msg_len = RSA_public_encrypt(msg_len, msg, *enc_msg, rsa, RSA_PKCS1_PADDING);
tappa_printf("Enc msg len%d", sizeof *enc_msg_len);
if (*enc_msg_len == -1) {
fprintf(stderr, "Encryption error\n");
free(*enc_msg);
RSA_free(rsa);
BIO_free(bio);
return -1;
}
ret = 0;
tappa_printf("ret msg %d", ret);
RSA_free(rsa);
BIO_free(bio);
return ret;
}
I tried another approach I saw on the internet, reading the key from a file however, I am faced with "No such file/directory" error after adding the file to my project
FILE* fp =fopen("public.txt","r");
if(fp==NULL){
perror("file error");
return NULL;
}
UPDATE 1:: After I upgraded to the more recent functions for keygen, encrypting and decrypting
I have no issues when I generate the key used with this code
int rsaEvpKeyGen(EVP_PKEY **outPK){
EVP_PKEY_CTX * ctx = NULL;
EVP_PKEY * pkey = NULL;
EVP_PKEY * rkey = NULL;
size_t outlen = 0;
size_t doutlen = 0;
unsigned char * encrypted = NULL;
unsigned char * decrypted = NULL;
// Create context
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (!ctx) {
cleanup(ctx, pkey, encrypted, decrypted);
return 1;
}
// Init key generator
if (EVP_PKEY_keygen_init(ctx) <= 0) {
cleanup(ctx, pkey, encrypted, decrypted);
return 2;
}
int KEY_BITS = 4096;
// Configure key generation
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KEY_BITS) <= 0) {
cleanup(ctx, pkey, encrypted, decrypted);
return 3;
}
// Create keys
if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
cleanup(ctx, pkey, encrypted, decrypted);
return 4;
}
*outPK =pkey;
return 0;
}
However, when I try to create a EVP_PKEY from my key string, it fails as my EVP_PKEY_new_raw_public_key returns NULL
int rsaEvpEncrypt(const char * data){
EVP_PKEY_CTX * ctx = NULL;
const char *pub_key_str = "-----BEGIN PUBLIC KEY-----\n"
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1WRkqlsZbmaNWbOZr/M4\n"
"ddwXKjmHUJ8zn9hy0WE15A8T4xjNuzJ/ATKbz20/2wVgFEGTWPNCUjkUvFzr3mhN\n"
"v/YJXo5iXQDP3M6rVv/w3SXaiR9C8yhDxWUz7WX/clmj7vDvk5Iydgq07gVScx+\n"
"5DBVbGAx10jHSDSa7f/NXMn9yNHad4hN0i1l+tsTB39CFsNeVFr349y4R8HA5Zma\n"
"EKseLc8iKzkwEQcuyMn4znaFpnOL0CmSrYB5K1E9zmmtDhMvDs540ZotcH/xpJiV\n"
"XEdQOWGFN6rryeAKP8y+sMRfQLxoiTg37YfsxTscf0gzKBizoamTF3TjRUNM55aE\n"
"BwIDAQAB\n"
"-----END PUBLIC KEY-----";
EVP_PKEY * pkey = NULL;
pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_RSA,NULL, (const unsigned char *) pub_key_str,
strlen(pub_key_str) );
unsigned char * encrypted = NULL;
size_t outlen = 0;
const size_t inlen = strlen(data);
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx) {
cleanup(ctx, NULL, NULL, NULL);
return 1000;
}
// Init encryption context.
if (EVP_PKEY_encrypt_init(ctx) <= 0) { // <-SEGFAULT LINE
cleanup(ctx, pkey, encrypted, NULL);
return 5;
}
// Defines padding
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
cleanup(ctx, pkey, encrypted, NULL);
return 6;
}
// Evaluating size for encrypted buffer and encrypting
if (EVP_PKEY_encrypt(ctx, NULL, &outlen, (const unsigned char *) data, inlen) <= 0) {
cleanup(ctx, pkey, encrypted, NULL);
return 7;
}
encrypted = (unsigned char *)OPENSSL_malloc(outlen);
if (!encrypted) {
cleanup(ctx, pkey, encrypted, NULL);
return 8;
}
if (EVP_PKEY_encrypt(ctx, encrypted, &outlen, (const unsigned char *)data, inlen) <= 0) {
cleanup(ctx, pkey, encrypted, NULL);
return 9;
}
tappa_print("\"ENCRYPTED: %s", (char *)encrypted);
}
First off, the low-level (i.e. algorithm-specific) APIs like RSA_public_encrypt
are deprecated in OpenSSL 3 (and mostly have been since 1.1.0 at least). Although they haven't yet been deleted.
Second, your 'typescript' (I assume actually nodejs) code is encrypting using RSA-OAEP, (correction) which your code doesn't, although I never noticed before the old API can do OAEP using unadjustable default parameters -- but you still shouldn't.
(added) As the man page for RSA_public_encrypt,RSA_private_decrypt
tells you, the preferred method (since 1.0.0 in 2010) is the high-level interface EVP_PKEY_encrypt_init[_ex]
and EVP_PKEY_encrypt
-- which can do OAEP including (optional) parameters, and in fact the man page for those functions even contains an example for RSA-OAEP (without parameters).
Third, the value of errno
is only useful for (some) C-stdlib calls (mostly those that involve an OS call); after a failed operation on a mem_BIO it is irrelevant and useless.
Fourth, sizeof *enc_msg
where enc_msg
is unsigned char **
is the size of the pointer, a fixed value today normally 8, not the size of the malloc'ed data it points to, which is probably what you want here. Similarly sizeof *enc_msg_len
on size_t *
is a fixed value today normally 8, not the size of the RSA ciphertext. Plus if your tappa_printf
is using the same specifiers as stdio {,v}{,f,s}printf
, %d
is not actually correct for size_t
, though on many platforms it will 'accidentally' work. %zu
is guaranteed.
All that said, your code works for me given a publickey that is actually in PEM format, and a valid (i.e. small) message. You post only 'something like' your publickey, so I can't say for sure what's wrong with it, but it definitely must have linebreaks (in C, newline characters) between the BEGIN line and the body and the body and the END line, it may need to have linebreaks within the body (actual PEM required them every 64, OpenSSL below 1.1.0 required every 76 or less but a multiple of 4; since then the limit has increased but it's still not infinite, nor documented that I've found), and embedded spaces are not guaranteed although some may work sometimes.