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

Я пытаюсь разобраться с шифрованием с открытым ключом, используя реализацию rsa openssl в 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 должен быть отправлен Бобу? Должно ли оно быть переосмыслено в массив символов и затем отправлено стандартным способом? Я что-то слышал о сертификатах - они имеют к этому какое-то отношение?

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

РЕДАКТИРОВАТЬ: 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,"%s\r\n",BN_bn2hex(rsa->n));
    sprintf(keyBufferB,"%s\r\n",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 ]

27 голосов
/ 13 января 2010

На самом деле вы должны использовать высокоуровневые функции «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;
}
0 голосов
/ 11 июня 2013

Спасибо @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. Работало нормально.

0 голосов
/ 11 октября 2012

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

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

Взять файл в качестве ввода: https://github.com/farslan/snippets/blob/master/hybrid_file.c

Взять строковый буфер в качестве ввода: https://github.com/farslan/snippets/blob/master/hybrid_data.c

0 голосов
/ 06 января 2010

На самом деле, нет проблем, я только что прочитал, что в основном объект 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 * для публичного шифрования симметричного шифровального ключа, который затем может быть отправлен Алисе. Алиса может затем расшифровать с помощью закрытого ключа

Бен.

...