Можете ли вы помочь мне разобраться с шифрованием открытого ключа openssl с помощью rsa.h в c++?

Я пытаюсь обойти шифрование с открытым ключом, используя реализацию openssl rsa на C++. Вы можете помочь? Пока это мои мысли (пожалуйста, исправьте, если необходимо)

  1. Алиса подключена к Бобу по сети
  2. Алиса и Боб хотят безопасной связи
  3. Алиса генерирует пару открытый / закрытый ключ и отправляет открытый ключ Боба
  4. Боб получает открытый ключ и шифрует случайно сгенерированный симметричный ключ шифрования (например, blowfish) с открытым ключом и отправляет результат Алисе
  5. Алиса расшифровывает зашифрованный текст с помощью первоначально созданного закрытого ключа и получает симметричный ключ blowfish
  6. Алиса и Боб теперь оба имеют знание симметричного ключа blowfish и могут установить безопасный канал связи

Теперь я посмотрел на openssl / rsa.H реализация rsa (так как у меня уже есть практический опыт работы с openssl/blowfish.h), И я вижу этих двух функции:

int RSA_public_encrypt(int flen, unsigned char *from,
unsigned char *to, RSA *rsa, int padding);
int RSA_private_decrypt(int flen, unsigned char *from,
 unsigned char *to, RSA *rsa, int padding);

если Алиса должна генерировать *rsa, как это дает пару ключей rsa? Есть ли что-то вроде rsa_public и rsa_private, которые получены из rsa? Содержит ли * rsa как открытый, так и закрытый ключ, и вышеуказанная функция автоматически удаляет необходимый ключ в зависимости от того, требуется ли ему публичная или частная часть? Если два уникальных указателя *rsa генерируются так, что на самом деле у нас есть следующее:

int RSA_public_encrypt(int flen, unsigned char *from,
unsigned char *to, RSA *rsa_public, int padding);
int RSA_private_decrypt(int flen, unsigned char *from,
 unsigned char *to, RSA *rsa_private, int padding);

во-вторых, в каком формате должен ли открытый ключ * rsa быть отправлен Бобу? Должен ли он быть переинтерпретирован в массив символов, а затем отправлен стандартным способом? Я что-то слышал о сертификатах-они имеют к этому какое-то отношение?

извините за все вопросы, наилучшие пожелания, Бен.

EDIT: Coe в настоящее время я использую:

/*
 *  theEncryptor.cpp
 *  
 *
 *  Created by ben on 14/01/2010.
 *  Copyright 2010 __MyCompanyName__. All rights reserved.
 *
 */

#include "theEncryptor.h"
#include <iostream>
#include <sys/socket.h>
#include <sstream>

theEncryptor::theEncryptor()
{

}

void
theEncryptor::blowfish(unsigned char *data, int data_len, unsigned char* key, int enc)
{

    //  hash the key first! 
    unsigned char obuf[20];
    bzero(obuf,20);
    SHA1((const unsigned char*)key, 64, obuf);

    BF_KEY bfkey;
    int keySize = 16;//strlen((char*)key);
    BF_set_key(&bfkey, keySize, obuf);

    unsigned char ivec[16];
    memset(ivec, 0, 16);

    unsigned char* out=(unsigned char*) malloc(data_len);
    bzero(out,data_len);
    int num = 0;
    BF_cfb64_encrypt(data, out, data_len, &bfkey, ivec, &num, enc);

    //for(int i = 0;i<data_len;i++)data[i]=out[i];

    memcpy(data, out, data_len);
    free(out);  

}

void
theEncryptor::generateRSAKeyPair(int bits)
{
    rsa = RSA_generate_key(bits, 65537, NULL, NULL);
}


int
theEncryptor::publicEncrypt(unsigned char* data, unsigned char* dataEncrypted,int dataLen)
{   
    return RSA_public_encrypt(dataLen, data, dataEncrypted, rsa, RSA_PKCS1_OAEP_PADDING);   
}

int
theEncryptor::privateDecrypt(unsigned char* dataEncrypted,
                             unsigned char* dataDecrypted)
{
    return RSA_private_decrypt(RSA_size(rsa), dataEncrypted, 
                                   dataDecrypted, rsa, RSA_PKCS1_OAEP_PADDING);
}

void 
theEncryptor::receivePublicKeyAndSetRSA(int sock, int bits)
{
    int max_hex_size = (bits / 4) + 1;
    char keybufA[max_hex_size];
    bzero(keybufA,max_hex_size);
    char keybufB[max_hex_size];
    bzero(keybufB,max_hex_size);
    int n = recv(sock,keybufA,max_hex_size,0); 
    n = send(sock,"OK",2,0);
    n = recv(sock,keybufB,max_hex_size,0); 
    n = send(sock,"OK",2,0); 
    rsa = RSA_new();
    BN_hex2bn(&rsa->n, keybufA);
    BN_hex2bn(&rsa->e, keybufB);
}

void 
theEncryptor::transmitPublicKey(int sock, int bits)
{
    const int max_hex_size = (bits / 4) + 1;
    long size = max_hex_size;
    char keyBufferA[size];
    char keyBufferB[size];
    bzero(keyBufferA,size);
    bzero(keyBufferB,size);
    sprintf(keyBufferA,"%srn",BN_bn2hex(rsa->n));
    sprintf(keyBufferB,"%srn",BN_bn2hex(rsa->e));
    int n = send(sock,keyBufferA,size,0);
    char recBuf[2];
    n = recv(sock,recBuf,2,0);
    n = send(sock,keyBufferB,size,0);
    n = recv(sock,recBuf,2,0);
}

void
theEncryptor::generateRandomBlowfishKey(unsigned char* key, int bytes)
{
            /*
    srand( (unsigned)time( NULL ) );
    std::ostringstream stm;
    for(int i = 0;i<bytes;i++){
        int randomValue = 65 + rand()% 26;
        stm << (char)((int)randomValue);
    }
    std::string str(stm.str());
    const char* strs = str.c_str();
    for(int i = 0;bytes;i++)key[i]=strs[i];
            */

    int n = RAND_bytes(key, bytes);

    if(n==0)std::cout<<"Warning key was generated with bad entropy. You should not consider communication to be secure"<<std::endl;

}

theEncryptor::~theEncryptor(){}

4 ответов


на самом деле вы должны использовать функции шифрования конвертов более высокого уровня от 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.

как только вы это сделаете, чтобы решить проблему аутентификации, о которой я упомянул в своем комментарии, Вам нужно будет установить доверенный орган ("Trent"). Открытый ключ Трента известен всем пользователям (распространяется с приложением или подобным - просто загрузите его из файла PEM). Вместо обмена голой RSA параметры, Алиса и Боб обмениваются сертификатами x509, которые содержат их открытые ключи RSA вместе с их именем и подписаны Трентом. Затем Алиса и Боб проверяют сертификат, который они получили от другого (используя открытый ключ Трента, который они уже знают), включая проверку того, что связанное имя является правильным, прежде чем продолжить протокол. OpenSSL включает функции для загрузки и проверки сертификатов в x509.h заголовок.


вот пример использования EVP_Seal*() для шифрования файла с открытым ключом получателя. Он принимает файл открытого ключа PEM RSA (т. е. как сгенерированный openssl rsa -pubout) в качестве аргумента командной строки, читает исходные данные из stdin и записывает зашифрованные данные в stdout. Для расшифровки используйте EVP_Open*() и PEM_read_RSAPrivateKey() для чтения закрытого ключа, а не открытого ключа.

на самом деле это не так сложно - и, конечно, менее подвержено ошибкам, чем возиться с генерацией заполнения, IVs и так далее ( Функция уплотнения выполняет как 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_*() функции сами генерируют необходимые случайные числа, используя криптографически сильный RNG, засеянный из соответствующего источника энтропии).

  • вы устанавливаете IV для режима CFB на фиксированное значение (ноль). Это отрицает любое преимущество использования режима CFB в первую очередь (позволяя злоумышленникам тривиально выполнять атаки с заменой блоков и хуже). (The 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;
}

На самом деле, нет проблем, я только что прочитал, что в основном объект RSA-это структура, содержащая как общедоступные, так и частные поля. Можно извлечь данные открытого поля и отправить их только Бобу.

т. е. в принципе, чтобы извлечь общедоступные поля из rsa и сохранить их в двух разных буферах (которые являются массивами символов и могут быть отправлены Бобу), вы делаете:

sprintf(keyBufferA,"%s\r\n",BN_bn2hex(rsa->n));
sprintf(keyBufferB,"%s\r\n",BN_bn2hex(rsa->e));

и затем Боб, на приемном конце, реконструирует следующим образом:

rsa = RSA_new();
BN_hex2bn(&rsa->n, keybufA);
BN_hex2bn(&rsa->e, keybufB);

Боб затем можно использовать rsa* для публичного шифрования симметричного ключа шифрования, который затем может быть отправлен Алисе. Алиса может расшифровать с помощью закрытого ключа

Бен.


Я пишу два примеры вокруг кода CAF. Они сильно модифицированы и используют OpenSSL BIO контейнер для большей абстракции.

в одном примере в качестве входных данных используется файл, а в другом-строковый буфер. Он использует RSA и DES, однако, вы можете легко изменить код. Инструкции по компиляции находятся внутри кода. Мне нужен рабочий пример, надеюсь кто-то найдет это полезным. Я также прокомментировал код. Вы можете получить его от здесь:

забрать в качестве входных данных: https://github.com/farslan/snippets/blob/master/hybrid_file.c

забрать string buffer в качестве входных данных: https://github.com/farslan/snippets/blob/master/hybrid_data.c


Спасибо @Caf. Твой пост помог. Однако я получил

программа ' [7056] Encryption2.exe: Native ' вышел с кодом -1073741811 (0xc000000d) для строки

  PEM_read_RSA_PUBKEY(rsa_pkey_file, &rsa_pkey, NULL, NULL)

изменился на

BIO *bio;
X509 *certificate;

bio = BIO_new(BIO_s_mem());
BIO_puts(bio, (const char*)data);
certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL);
EVP_PKEY *pubkey = X509_get_pubkey (certificate);
rsa_pkey = EVP_PKEY_get1_RSA(pubkey);

где данные имеют файл PEM только с открытым ключом. Моя задача заключалась в шифровании на C++ и расшифровке на java. Я передал base64 закодированный ek размера eklen (я не использовал eklen_n) и расшифровал, чтобы получить ключ AES, используя закрытый ключ RSA. Тогда Я расшифровать зашифрованный файл с помощью этого ключа AES. Сработало отлично.