На самом деле вы должны использовать высокоуровневые функции «Envelope Encryption» из openssl/evp.h
, а не низкоуровневые функции RSA напрямую. Они делают большую часть работы за вас и означают, что вам не нужно изобретать велосипед.
В этом случае вы будете использовать функции EVP_SealInit()
, EVP_SealUpdate()
и EVP_SealFinal()
. Соответствующими функциями дешифрования являются EVP_OpenInit()
, EVP_OpenUpdate()
и EVP_OpenFinal()
. Я бы предложил использовать EVP_aes_128_cbc()
в качестве значения параметра типа шифра.
Как только вы загрузили открытый ключ в дескриптор RSA *
, вы используете EVP_PKEY_assign_RSA()
, чтобы поместить его в дескриптор EVP_PKEY *
для функций EVP.
Как только вы это сделаете, для решения проблемы аутентификации, о которой я упоминал в своем комментарии, вам необходимо установить доверенный орган («Трент»). Открытый ключ Трента известен всем пользователям (распространяется вместе с приложением или аналогичным способом - просто загрузите его из файла PEM). Вместо обмена пустыми параметрами RSA Алиса и Боб обмениваются сертификатами x509, которые содержат свои открытые ключи RSA вместе со своим именем и подписаны Трентом. Затем Алиса и Боб проверяют сертификат, который они получили от другого (используя открытый ключ Трента, который они уже знают), включая проверку правильности связанного имени, прежде чем продолжить протокол. OpenSSL включает функции для загрузки и проверки сертификатов в заголовке x509.h
.
Вот пример того, как использовать EVP_Seal*()
для шифрования файла с использованием открытого ключа получателя. Он принимает файл открытого ключа PEM RSA (т. Е. Сгенерированный openssl rsa -pubout
) в качестве аргумента командной строки, считывает исходные данные из stdin и записывает зашифрованные данные в stdout. Для расшифровки используйте EVP_Open*()
вместо и PEM_read_RSAPrivateKey()
для чтения закрытого ключа, а не открытого ключа.
Это на самом деле не так сложно - и, конечно, менее подвержено ошибкам, чем возиться с генерацией отступов, IV и т. Д. (Функция Seal выполняет как RSA, так и AES часть сделки). В любом случае, код:
#include <stdio.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <arpa/inet.h> /* For htonl() */
int do_evp_seal(FILE *rsa_pkey_file, FILE *in_file, FILE *out_file)
{
int retval = 0;
RSA *rsa_pkey = NULL;
EVP_PKEY *pkey = EVP_PKEY_new();
EVP_CIPHER_CTX ctx;
unsigned char buffer[4096];
unsigned char buffer_out[4096 + EVP_MAX_IV_LENGTH];
size_t len;
int len_out;
unsigned char *ek;
int eklen;
uint32_t eklen_n;
unsigned char iv[EVP_MAX_IV_LENGTH];
if (!PEM_read_RSA_PUBKEY(rsa_pkey_file, &rsa_pkey, NULL, NULL))
{
fprintf(stderr, "Error loading RSA Public Key File.\n");
ERR_print_errors_fp(stderr);
retval = 2;
goto out;
}
if (!EVP_PKEY_assign_RSA(pkey, rsa_pkey))
{
fprintf(stderr, "EVP_PKEY_assign_RSA: failed.\n");
retval = 3;
goto out;
}
EVP_CIPHER_CTX_init(&ctx);
ek = malloc(EVP_PKEY_size(pkey));
if (!EVP_SealInit(&ctx, EVP_aes_128_cbc(), &ek, &eklen, iv, &pkey, 1))
{
fprintf(stderr, "EVP_SealInit: failed.\n");
retval = 3;
goto out_free;
}
/* First we write out the encrypted key length, then the encrypted key,
* then the iv (the IV length is fixed by the cipher we have chosen).
*/
eklen_n = htonl(eklen);
if (fwrite(&eklen_n, sizeof eklen_n, 1, out_file) != 1)
{
perror("output file");
retval = 5;
goto out_free;
}
if (fwrite(ek, eklen, 1, out_file) != 1)
{
perror("output file");
retval = 5;
goto out_free;
}
if (fwrite(iv, EVP_CIPHER_iv_length(EVP_aes_128_cbc()), 1, out_file) != 1)
{
perror("output file");
retval = 5;
goto out_free;
}
/* Now we process the input file and write the encrypted data to the
* output file. */
while ((len = fread(buffer, 1, sizeof buffer, in_file)) > 0)
{
if (!EVP_SealUpdate(&ctx, buffer_out, &len_out, buffer, len))
{
fprintf(stderr, "EVP_SealUpdate: failed.\n");
retval = 3;
goto out_free;
}
if (fwrite(buffer_out, len_out, 1, out_file) != 1)
{
perror("output file");
retval = 5;
goto out_free;
}
}
if (ferror(in_file))
{
perror("input file");
retval = 4;
goto out_free;
}
if (!EVP_SealFinal(&ctx, buffer_out, &len_out))
{
fprintf(stderr, "EVP_SealFinal: failed.\n");
retval = 3;
goto out_free;
}
if (fwrite(buffer_out, len_out, 1, out_file) != 1)
{
perror("output file");
retval = 5;
goto out_free;
}
out_free:
EVP_PKEY_free(pkey);
free(ek);
out:
return retval;
}
int main(int argc, char *argv[])
{
FILE *rsa_pkey_file;
int rv;
if (argc < 2)
{
fprintf(stderr, "Usage: %s <PEM RSA Public Key File>\n", argv[0]);
exit(1);
}
rsa_pkey_file = fopen(argv[1], "rb");
if (!rsa_pkey_file)
{
perror(argv[1]);
fprintf(stderr, "Error loading PEM RSA Public Key File.\n");
exit(2);
}
rv = do_evp_seal(rsa_pkey_file, stdin, stdout);
fclose(rsa_pkey_file);
return rv;
}
Код, который вы опубликовали, прекрасно иллюстрирует, почему вы должны использовать функции более высокого уровня - вы попали в пару ловушек:
rand()
категорически не криптографически сильный генератор случайных чисел! Генерации вашего симметричного ключа с помощью rand()
достаточно, чтобы сделать всю систему совершенно небезопасной. (Функции EVP_*()
сами генерируют необходимые случайные числа, используя криптографически сильный ГСЧ, отобранный из соответствующего источника энтропии).
Вы устанавливаете IV для режима CFB на фиксированное значение (ноль). Это сводит на нет любое преимущество использования режима CFB в первую очередь (позволяя злоумышленникам тривиально выполнять атаки с заменой блоков и даже хуже). (Функции EVP_*()
генерируют соответствующий IV для вас, когда это необходимо).
RSA_PKCS1_OAEP_PADDING
следует использовать, если вы определяете новый протокол, а не взаимодействуете с существующим протоколом.
Соответствующий код расшифровки, для потомков:
#include <stdio.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <arpa/inet.h> /* For htonl() */
int do_evp_unseal(FILE *rsa_pkey_file, FILE *in_file, FILE *out_file)
{
int retval = 0;
RSA *rsa_pkey = NULL;
EVP_PKEY *pkey = EVP_PKEY_new();
EVP_CIPHER_CTX ctx;
unsigned char buffer[4096];
unsigned char buffer_out[4096 + EVP_MAX_IV_LENGTH];
size_t len;
int len_out;
unsigned char *ek;
unsigned int eklen;
uint32_t eklen_n;
unsigned char iv[EVP_MAX_IV_LENGTH];
if (!PEM_read_RSAPrivateKey(rsa_pkey_file, &rsa_pkey, NULL, NULL))
{
fprintf(stderr, "Error loading RSA Private Key File.\n");
ERR_print_errors_fp(stderr);
retval = 2;
goto out;
}
if (!EVP_PKEY_assign_RSA(pkey, rsa_pkey))
{
fprintf(stderr, "EVP_PKEY_assign_RSA: failed.\n");
retval = 3;
goto out;
}
EVP_CIPHER_CTX_init(&ctx);
ek = malloc(EVP_PKEY_size(pkey));
/* First need to fetch the encrypted key length, encrypted key and IV */
if (fread(&eklen_n, sizeof eklen_n, 1, in_file) != 1)
{
perror("input file");
retval = 4;
goto out_free;
}
eklen = ntohl(eklen_n);
if (eklen > EVP_PKEY_size(pkey))
{
fprintf(stderr, "Bad encrypted key length (%u > %d)\n", eklen,
EVP_PKEY_size(pkey));
retval = 4;
goto out_free;
}
if (fread(ek, eklen, 1, in_file) != 1)
{
perror("input file");
retval = 4;
goto out_free;
}
if (fread(iv, EVP_CIPHER_iv_length(EVP_aes_128_cbc()), 1, in_file) != 1)
{
perror("input file");
retval = 4;
goto out_free;
}
if (!EVP_OpenInit(&ctx, EVP_aes_128_cbc(), ek, eklen, iv, pkey))
{
fprintf(stderr, "EVP_OpenInit: failed.\n");
retval = 3;
goto out_free;
}
while ((len = fread(buffer, 1, sizeof buffer, in_file)) > 0)
{
if (!EVP_OpenUpdate(&ctx, buffer_out, &len_out, buffer, len))
{
fprintf(stderr, "EVP_OpenUpdate: failed.\n");
retval = 3;
goto out_free;
}
if (fwrite(buffer_out, len_out, 1, out_file) != 1)
{
perror("output file");
retval = 5;
goto out_free;
}
}
if (ferror(in_file))
{
perror("input file");
retval = 4;
goto out_free;
}
if (!EVP_OpenFinal(&ctx, buffer_out, &len_out))
{
fprintf(stderr, "EVP_OpenFinal: failed.\n");
retval = 3;
goto out_free;
}
if (fwrite(buffer_out, len_out, 1, out_file) != 1)
{
perror("output file");
retval = 5;
goto out_free;
}
out_free:
EVP_PKEY_free(pkey);
free(ek);
out:
return retval;
}
int main(int argc, char *argv[])
{
FILE *rsa_pkey_file;
int rv;
if (argc < 2)
{
fprintf(stderr, "Usage: %s <PEM RSA Private Key File>\n", argv[0]);
exit(1);
}
rsa_pkey_file = fopen(argv[1], "rb");
if (!rsa_pkey_file)
{
perror(argv[1]);
fprintf(stderr, "Error loading PEM RSA Private Key File.\n");
exit(2);
}
rv = do_evp_unseal(rsa_pkey_file, stdin, stdout);
fclose(rsa_pkey_file);
return rv;
}