2010-01-06 1 views
9

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

  1. Алиса подключается к Бобу через сеть
  2. Алиса и Боб хотят безопасной связи
  3. Алиса генерирует пару открытый/закрытый ключ и посылает общественности ключ к Бобу
  4. Боб получает открытый ключ и шифрует случайно сгенерированный симметричный Cypher ключ (например, 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_public и rsa_private, которые получены из 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: Коу я в настоящее время занято:

/* 
* 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(){} 
+0

Обратите внимание, что ваша предлагаемая схема уязвима для этого: 3a) Ева перехватывает открытый ключ Алисы. 3b) Eve генерирует пару открытых/закрытых ключей и отправляет открытый ключ Бобу, заявляя, что является Алисой. 4a) Ева перехватывает зашифрованный симметричный ключ и расшифровывает его своим личным ключом. 4b) Eve повторно шифрует симметричный ключ открытым ключом Алисы и отправляет его Алисе, заявляя, что она Боб. Алиса, Боб и Ева (неизвестные Алисе и Бобу) теперь все имеют одинаковый симметричный ключ. Ева может расшифровать все последующие данные, отправленные Алисой и Бобом. – caf

+0

Боже, ты совершенно прав. Благодарим за указание :-) –

+0

Я обновил свой ответ, рассказывая, как использовать функции 'EVP_Seal *()', они очень хороши, как только у вас есть рабочий пример, чтобы посмотреть, это небольшая документация не хватает. – caf

ответ

28

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

Как только вы это сделаете, чтобы решить проблему аутентификации, о которой я упоминал в своем комментарии, вам необходимо создать доверенный орган («Trent»). Открытый ключ Trent известен всем пользователям (распространяется вместе с приложением или похожим - просто загружает его из файла PEM). Вместо обмена общими параметрами RSA Алиса и Боб обмениваются сертификатами x509, которые содержат их открытые ключи RSA вместе с их именем, и подписываются Trent. Алиса и Боб затем проверяют сертификат, который они получили от другого (используя открытый ключ Trent, который они уже знают), включая проверку того, что соответствующее имя является правильным, перед продолжением протокола. 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_*() генерируют сами необходимые случайные числа, используя криптографически сильный RNG, высеваемый из соответствующего источника энтропии).

  • Вы устанавливаете режим 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

Еще раз спасибо, кафе, ваш вклад в значительной степени оценен. Тем не менее, я смотрел на вызовы EVP раньше, но по какой-то причине они кажутся слишком затянутыми и сложными, и на самом деле их гораздо проще, с внедрением, IMHO, вызвать функции RSA и фактически многие функции openssl напрямую , Я понимаю, как это сделать, но я не понимаю, как использовать вызовы EVP. –

+0

Что касается предмета сертификата, это, безусловно, имеет смысл.Вчера я прочитал, что, учитывая первоначальную схему, изложенную выше, Алиса должна подписать свой открытый ключ, который затем может быть проверен Боба на предмет подлинности. Я также понял, что это не будет превышать описанную выше уязвимость, почему его необходимо публиковать открытый ключ и хранить его как сертификат с доверенным органом. –

+0

Спасибо за ваш код caf. Но я все же думаю, что его проще обойти функции EVP и напрямую вызвать вещи ... но потом, Я просто хочу научиться их использовать ... Я редактировал свой оригинальный пост с классом, который инкапсулирует способ обработки материала ... Привет. –

0

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

I.e. в основном, для извлечения открытых полей из РКА и хранить каждый в двух различных буферов (которые являются массивами символов, а затем могут быть отправлены Бобу), вы делаете:

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); 

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

Ben.

+0

Обратите внимание, что очень хорошее руководство можно найти здесь: http://www.rohitab.com/discuss/index.php?showtopic=28473 –

0

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

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

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

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

0

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