2010-01-06 14 views
9

Sto cercando di capire come utilizzare la crittografia a chiave pubblica utilizzando l'implementazione openssl di rsa in C++. Puoi aiutare? Finora questi sono i miei pensieri (si prega di fare corretta se necessario)Puoi aiutarmi a capire meglio la crittografia a chiave pubblica openssl con rsa.h in C++?

  1. Alice è collegato a Bob su una rete
  2. Alice e Bob vogliono comunicazioni sicure
  3. Alice genera una coppia di chiavi pubblica/privata e invia pubblico chiave per Bob
  4. Bob riceve la chiave pubblica e crittografa una chiave cifra simmetrica generata in modo casuale (ad esempio, Blowfish) con la chiave pubblica e invia il risultato ad Alice
  5. Alice decifra il testo cifrato con la chiave privata originariamente generato e ottiene il pesce palla simmetrica chiave
  6. Alice e Bob ora entrambi hanno una conoscenza della chiave simmetrica Blowfish e possono stabilire un canale di comunicazione sicuro

ora, ho guardato il/rsa.h rsa implementazione OpenSSL (dato che già hanno esperienza pratica con OpenSSL /blowfish.h), e vedo queste due funzioni:

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

Se Alice è quello di generare * rsa, come fa questo rendimento della coppia di chiavi RSA? C'è qualcosa come rsa_public e rsa_private che derivano da rsa? * Rsa contiene sia la chiave pubblica che quella privata e la funzione sopra descritta rimuove automaticamente la chiave necessaria a seconda che richieda la parte pubblica o privata? Se due unici * puntatori rsa essere generati in modo che in realtà, abbiamo la seguente:

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

In secondo luogo, in quale formato dovrebbe il * chiave pubblica RSA da inviare a Bob? Deve essere reinterpretato in un array di caratteri e quindi inviato in modo standard? Ho sentito qualcosa sui certificati: hanno qualcosa a che fare con questo?

Ci scusiamo per tutte le domande, Auguri, Ben.

EDIT: Coe Attualmente sto impiegando:

/* 
* 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

Si noti che lo schema proposto è vulnerabile a questo: 3a) Eve intercetta la chiave pubblica di Alice. 3b) Eve genera una coppia di chiavi pubblica/privata e invia la chiave pubblica a Bob, affermando di essere Alice. 4a) Eve intercetta la chiave simmetrica crittografata e la decrittografa con la sua chiave privata. 4b) Eve ricodifica la chiave simmetrica con la chiave pubblica di Alice e la invia ad Alice, dichiarando di essere Bob. Alice, Bob ed Eve (a insaputa di Alice e Bob) ora condividono tutti la stessa chiave simmetrica. Eve è libero di decodificare tutti i dati successivi inviati tra Alice e Bob. – caf

+0

Dio, hai assolutamente ragione. Grazie gentilmente per avermelo fatto notare :-) –

+0

Ho aggiornato la mia risposta mostrando come usare le funzioni 'EVP_Seal *()' - sono abbastanza buone una volta che hai un esempio funzionante da guardare, è la documentazione che è un po ' carente. – caf

risposta

28

Si dovrebbero effettivamente utilizzare le funzioni di "Envelope Encryption" di livello superiore da openssl/evp.h, piuttosto che le funzioni RSA di basso livello direttamente.Questi fanno la maggior parte del lavoro per te e significano che non devi reinventare la ruota.

In questo caso, utilizzare le funzioni EVP_SealInit(), EVP_SealUpdate() e EVP_SealFinal(). Le funzioni di decodifica corrispondenti sono EVP_OpenInit(), EVP_OpenUpdate() e EVP_OpenFinal(). Suggerirei di utilizzare EVP_aes_128_cbc() come valore del parametro del tipo di cifratura.

Dopo aver caricato la chiave pubblica in una maniglia RSA *, utilizzare EVP_PKEY_assign_RSA() per inserirla in una maniglia EVP_PKEY * per le funzioni EVP.

Una volta avviato, per risolvere il problema di autenticazione che ho menzionato nel mio commento, è necessario stabilire un'autorità attendibile ("Trent"). La chiave pubblica di Trent è nota a tutti gli utenti (distribuiti con l'applicazione o simili - è sufficiente caricarla da un file PEM). Invece di scambiare parametri RSA nudi, Alice e Bob scambiano i certificati x509 che contengono le loro chiavi pubbliche RSA insieme al loro nome e sono firmati da Trent. Alice e Bob quindi verificano ciascuno il certificato ricevuto dall'altro (utilizzando la chiave pubblica di Trento, che già conoscono), incluso il controllo che il nome associato è quello giusto, prima di continuare il protocollo. OpenSSL include funzioni per caricare e verificare i certificati nell'intestazione x509.h.


Ecco un esempio di come utilizzare EVP_Seal*() per crittografare un file data chiave pubblica del destinatario. Prende il file PEM RSA Public Key (cioè come generato da openssl rsa -pubout) come argomento della riga di comando, legge i dati di origine da stdin e scrive i dati crittografati su stdout. Per decodificare, utilizzare invece EVP_Open*() e PEM_read_RSAPrivateKey() per leggere una chiave privata anziché una chiave pubblica.

Non è poi così difficile - e certamente meno incline all'errore che fare confusione nel generare padding, IV e così via (la funzione Seal fa entrambe le parti dell'affare RSA e AES). In ogni caso, il codice:

#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; 
} 

Il codice che hai postato illustra bene il motivo per cui è necessario utilizzare le funzioni di livello superiore - siete caduti in un paio di insidie:

  • rand() è enfaticamente non un generatore di numeri casuali crittograficamente forte! Generare la tua chiave simmetrica usando rand() è sufficiente per rendere l'intero sistema completamente insicuro. (Le funzioni EVP_*() generano i numeri casuali necessari, utilizzando un RNG crittograficamente avanzato, inizializzato da un'origine di entropia appropriata).

  • Si imposta la modalità IV per CFB su un valore fisso (zero). Ciò annulla qualsiasi vantaggio dell'utilizzo della modalità CFB in primo luogo (consentendo agli aggressori di eseguire banalmente attacchi di sostituzione dei blocchi e peggio). (Le funzioni EVP_*() generano un IV appropriato per te, quando richiesto).

  • RSA_PKCS1_OAEP_PADDING deve essere utilizzato se si sta definendo un nuovo protocollo, piuttosto che l'interoperabilità con un protocollo esistente.


Il codice di decrittazione corrispondente, per i posteri:

#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

Grazie ancora caf, il tuo contributo è stato molto apprezzato. Comunque, ho già visto le chiamate all'EVP, ma per qualche ragione sembrano troppo lunghe e complicate e, in realtà, è molto più facile, IMHO, per implementare direttamente le funzioni RSA e molte delle funzioni di OpenSl direttamente. . Capisco come farlo, ma non capisco come utilizzare le chiamate EVP. –

+0

Per quanto riguarda la certificazione, ciò ha sicuramente senso.Ieri ho letto che, dato lo schema originale sopra delineato, Alice dovrebbe firmare la sua chiave pubblica che può essere controllata da Bob per autenticità, ho anche capito che questo non avrebbe superato la vulnerabilità che hai citato sopra, che immagino sia perché è necessario pubblicare la chiave pubblica e conservarla come certificato con un'autorità attendibile –

+0

Grazie per il tuo codice caf - ma ancora una volta, penso ancora che sia più semplice aggirare le funzioni EVP e chiamare le cose direttamente .... ma poi, Probabilmente ho solo bisogno di imparare a usarli ... Ho modificato il mio post originale con una classe che incapsula il modo in cui elaboro roba ... Saluti. –

0

In realtà, non c'è problema, ho appena letto che, in fondo, l'oggetto RSA è una struttura che contiene entrambi i campi pubblici e privati. Si possono estrarre i dati del campo pubblico e inviarli solo a Bob.

I.e. In sostanza, per estrarre i campi pubblici di RSA e memorizzare ciascuno in due tamponi diversi (che sono array di caratteri e possono poi essere inviato a Bob), si fa:

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

E poi Bob, sul lato sbagliato, ricostruisce come segue:

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

Bob può quindi utilizzare rsa * per crittografare pubblicamente la chiave cifra simmetrica che possono poi essere inviati ad Alice. Alice può quindi decifrare con la chiave privata

Ben.

+0

Nota che un ottimo tutorial è disponibile qui: http://www.rohitab.com/discuss/index.php?showtopic=28473 –

0

scrivo due esempi intorno il codice del CAF. Sono pesantemente modificati e utilizzano il contenitore BIO di OpenSSL per ulteriori astrazioni.

Un esempio utilizza un file come input e l'altro esempio utilizza un buffer di stringa. Utilizza RSA e DES, tuttavia è possibile modificarlo facilmente dal codice. Le istruzioni di compilazione sono contenute nel codice. Avevo bisogno di un esempio funzionante, spero che qualcuno lo trovi utile. Ho anche commentato il codice. È possibile ottenere da qui:

Prendere file di come input: https://github.com/farslan/snippets/blob/master/hybrid_file.c

Prendere tampone stringa come input: https://github.com/farslan/snippets/blob/master/hybrid_data.c

0

Grazie @Caf. Il tuo post ha aiutato Tuttavia ho avuto

Il programma '[7056] Encryption2.exe: Native' ha terminato con il codice di -1.073,741811 millions (0xC000000D) per la linea

PEM_read_RSA_PUBKEY(rsa_pkey_file, &rsa_pkey, NULL, NULL) 

ho cambiato

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

Se i dati ha il file PEM con solo chiave pubblica. La mia sfida era quella di criptare in C++ e decifrare in java. Ho trasmesso l'ek codificato base64 di dimensione eklen (non ho usato eklen_n) e decifrato per ottenere la chiave AES usando la chiave privata RSA. Quindi ho decrittografato il file di cifratura usando questo tasto AES. Ha funzionato bene.