你能帮我了解一下openssl公钥加密和rsa吗?h在c++中?

时间:2022-12-26 05:34:24

I am trying to get my head around public key encryption using the openssl implementation of rsa in C++. Can you help? So far these are my thoughts (please do correct if necessary)

我正试图用c++中的rsa的openssl实现来绕过公钥加密。你能帮助吗?到目前为止,这些都是我的想法(请在必要的时候做正确的事情)

  1. Alice is connected to Bob over a network
  2. Alice通过网络连接到Bob。
  3. Alice and Bob want secure communications
  4. 爱丽丝和鲍勃想要保密通信。
  5. Alice generates a public / private key pair and sends public key to Bob
  6. Alice生成一个公钥/私钥对,并向Bob发送公钥。
  7. Bob receives public key and encrypts a randomly generated symmetric cypher key (e.g. blowfish) with the public key and sends the result to Alice
  8. Bob接收公钥,并使用公钥对随机生成的对称密码匙(例如:blowfish)加密,并将结果发送给Alice。
  9. Alice decrypts the ciphertext with the originally generated private key and obtains the symmetric blowfish key
  10. 爱丽丝用原来生成的私钥解密密文,并获得对称的吹号键。
  11. Alice and Bob now both have knowledge of symmetric blowfish key and can establish a secure communication channel
  12. Alice和Bob现在都有关于对称吹鱼密钥的知识,并且可以建立一个安全的通信通道。

Now, I have looked at the openssl/rsa.h rsa implementation (since I already have practical experience with openssl/blowfish.h), and I see these two functions:

现在,我已经研究了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);

If Alice is to generate *rsa, how does this yield the rsa key pair? Is there something like rsa_public and rsa_private which are derived from rsa? Does *rsa contain both public and private key and the above function automatically strips out the necessary key depending on whether it requires the public or private part? Should two unique *rsa pointers be generated so that actually, we have the following:

如果Alice生成了*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);

Secondly, in what format should the *rsa public key be sent to Bob? Must it be reinterpreted in to a character array and then sent the standard way? I've heard something about certificates -- are they anything to do with it?

第二,rsa公钥应该以什么格式发送给Bob?是否必须将其重新解释为字符数组,然后发送标准的方法?我听说过一些关于证书的事情——它们和证书有关吗?

Sorry for all the questions, Best Wishes, Ben.

对不起,所有的问题,最好的祝福,本。

EDIT: Coe I am currently employing:

编辑:我目前正在使用的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 个解决方案

#1


28  

You should actually be using the higher-level "Envelope Encryption" functions from openssl/evp.h, rather than the low-level RSA functions directly. These do most of the work for you and mean you don't have to reinvent the wheel.

实际上,您应该使用openssl/evp的高级“信封加密”功能。h,而不是直接的低级RSA函数。这些都是为你做的工作,意味着你不必重新发明*。

In this case, you'd use the EVP_SealInit(), EVP_SealUpdate() and EVP_SealFinal() functions. The corresponding decryption functions are EVP_OpenInit(), EVP_OpenUpdate() and EVP_OpenFinal(). I would suggest using EVP_aes_128_cbc() as the value of the cipher type parameter.

在本例中,您将使用EVP_SealInit()、EVP_SealUpdate()和EVP_SealFinal()函数。对应的解密函数是EVP_OpenInit()、EVP_OpenUpdate()和EVP_OpenFinal()。我建议使用EVP_aes_128_cbc()作为密码类型参数的值。

Once you've got the public key loaded into an RSA * handle, you use EVP_PKEY_assign_RSA() to put it into an EVP_PKEY * handle for the EVP functions.

一旦将公共密钥加载到RSA *句柄中,就可以使用EVP_PKEY_assign_RSA()将其放入EVP_PKEY *句柄中,以处理EVP函数。

Once you've got this going, to solve the authentication problem I mentioned in my comment, you'll need to established a trusted authority ("Trent"). Trent's public key is known to all users (distributed with the application or similar - just load it from a PEM file). Instead of exchanging bare RSA parameters, Alice and Bob exchange x509 certificates that contain their RSA public keys together with their name, and are signed by Trent. Alice and Bob then each verify the certificate they recieved from the other (using Trent's public key, which they already know), including checking that the associated name is the right one, before continuing the protocol. OpenSSL includes functions for loading and verifying certificates in the x509.h header.

一旦您解决了这个问题,为了解决我在评论中提到的身份验证问题,您需要建立一个可信的权威(“Trent”)。所有用户都知道Trent的公钥(通过应用程序或类似的方式分发),只需从PEM文件中加载它即可。Alice和Bob交换x509证书,而不是交换光的RSA参数,这些证书包含他们的RSA公钥和他们的名字,并由Trent签名。然后,Alice和Bob分别验证他们收到的证书(使用Trent的公钥,他们已经知道),包括在继续协议之前检查关联的名称是否正确。OpenSSL包括在x509中装载和验证证书的功能。h头。


Here's an example of how to use EVP_Seal*() to encrypt a file given the recipient's public key. It takes the PEM RSA Public Key file (ie as generated by openssl rsa -pubout) as a command line argument, reads the source data from stdin and writes the encrypted data to stdout. To decrypt, use EVP_Open*() instead, and PEM_read_RSAPrivateKey() to read a private key rather than public key.

下面是一个如何使用EVP_Seal*()来加密给定接收者的公钥的文件的示例。它将PEM RSA公钥文件(由openssl RSA -pubout生成)作为命令行参数,从stdin读取源数据并将加密数据写入stdout。为了解密,可以使用EVP_Open*()和PEM_read_RSAPrivateKey()来读取私钥,而不是公钥。

It's not really that hard - and certainly less error prone than messing about generating padding, IVs and so on yourself (the Seal function does both the RSA and AES parts of the deal). Anyway, the code:

这并不是真的那么难——当然也不容易出错,而不是在生成填充、IVs等等方面的问题(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;
}

The code you've posted illustrates nicely why you should use the higher-level functions - you've fallen into a couple of pitfalls:

您发布的代码很好地说明了为什么要使用高级功能——您已经陷入了几个陷阱:

  • rand() is emphatically not a cryptographically strong random number generator! Generating your symmetric key using rand() is enough to make the entire system completely insecure. (The EVP_*() functions generate the necessary random numbers themselves, using a cryptographically strong RNG, seeded from an appropriate entropy source).

    rand()绝对不是一个密码强大的随机数生成器!使用rand()生成对称密钥就足以使整个系统完全不安全。(EVP_*()函数本身生成必要的随机数,使用加密强大的RNG,从适当的熵源中生成)。

  • You are setting the IV for CFB mode to a fixed value (zero). This negates any advantage of using CFB mode in the first place (allowing attackers to trivially perform block-replacement attacks and worse). (The EVP_*() functions generate an appropriate IV for you, when required).

    您将CFB模式的IV设置为一个固定值(零)。这就否定了首先使用CFB模式的任何好处(允许攻击者执行拦截攻击和更坏的攻击)。(EVP_*()函数在需要时为您生成适当的IV)。

  • RSA_PKCS1_OAEP_PADDING should be used if you're defining a new protocol, rather than interoperating with an existing protocol.

    如果您正在定义一个新的协议,而不是与现有的协议互操作,那么应该使用RSA_PKCS1_OAEP_PADDING。


The corresponding decryption code, for posterity:

对应的解密代码,为后代:

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

#2


0  

Actually, no problem, I have just read that basically, the RSA object is a structure that contains both public and private fields. One can extract the public field data and only send that to Bob.

实际上,没有问题,我刚读到,RSA对象是一个包含公共和私有字段的结构。可以提取公共字段数据,只将其发送给Bob。

I.e. basically, to extract the public fields from rsa and store each in two different buffers (which are char arrays and can then be sent to Bob), you do:

也就是说,从rsa中提取公共字段并将它们存储在两个不同的缓冲区中(它们是char数组,然后可以发送给Bob),您可以:

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

And then Bob, on the receiving end, reconstructs as follows:

然后Bob,在接收端,重构如下:

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

Bob can then use rsa* to publicly encrypt the symmetric cypher key which can then be sent to Alice. Alice can then decrypt with the private key

然后Bob可以使用rsa*公开加密对称的cypher密钥,然后将其发送给Alice。然后,Alice可以用私钥解密。

Ben.

本。

#3


0  

I write two examples around CAF's code. They are heavily modifed and uses OpenSSL's BIO container for more abstraction.

我在CAF的代码周围写了两个例子。它们被大量修改,并使用OpenSSL的BIO容器进行更多的抽象。

One example uses a file as input and the other example uses a string buffer. It uses RSA and DES, however you can easily change it from the code. Compile instructions are inside the code. I needed a working example, I hope someone find this useful. I also commented the code. You can get it from here:

一个示例使用一个文件作为输入,另一个示例使用一个字符串缓冲区。它使用RSA和DES,但是你可以很容易地从代码中更改它。编译指令在代码内部。我需要一个工作的例子,希望有人觉得这有用。我还注释了代码。你可以从这里得到:

Take file as input: https://github.com/farslan/snippets/blob/master/hybrid_file.c

以文件作为输入:https://github.com/farslan/snippets/blob/master/mixd_file.c。

Take string buffer as input: https://github.com/farslan/snippets/blob/master/hybrid_data.c

将字符串缓冲区作为输入:https://github.com/farslan/snippets/blob/master/mixd_data .c。

#4


0  

Thanks @Caf. Your post helped. However I got

谢谢@Caf。你的帮助。但是我有

The program '[7056] Encryption2.exe: Native' has exited with code -1073741811 (0xc000000d) for the line

程序的[7056]Encryption2。exe: Native已经退出了代码-1073741811 (0xc000000d)。

  PEM_read_RSA_PUBKEY(rsa_pkey_file, &rsa_pkey, NULL, NULL)

I changed to

我改变了

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

Where data has the PEM file with only public key. My challenge was to encrypt in C++ and decrypt in java. I transmitted the base64 encoded ek of size eklen (i did not use eklen_n) and decrypted to get the AES key using the RSA private key. Then I decrypted the cipher file using this AES key. It worked fine.

其中数据只有公钥的PEM文件。我的挑战是用c++加密并在java中解密。我传输了base64编码的eklen大小的ek(我没有使用eklen_n)和解密,以使用RSA私钥获取AES密钥。然后使用这个AES键解密密码文件。它工作得很好。

#1


28  

You should actually be using the higher-level "Envelope Encryption" functions from openssl/evp.h, rather than the low-level RSA functions directly. These do most of the work for you and mean you don't have to reinvent the wheel.

实际上,您应该使用openssl/evp的高级“信封加密”功能。h,而不是直接的低级RSA函数。这些都是为你做的工作,意味着你不必重新发明*。

In this case, you'd use the EVP_SealInit(), EVP_SealUpdate() and EVP_SealFinal() functions. The corresponding decryption functions are EVP_OpenInit(), EVP_OpenUpdate() and EVP_OpenFinal(). I would suggest using EVP_aes_128_cbc() as the value of the cipher type parameter.

在本例中,您将使用EVP_SealInit()、EVP_SealUpdate()和EVP_SealFinal()函数。对应的解密函数是EVP_OpenInit()、EVP_OpenUpdate()和EVP_OpenFinal()。我建议使用EVP_aes_128_cbc()作为密码类型参数的值。

Once you've got the public key loaded into an RSA * handle, you use EVP_PKEY_assign_RSA() to put it into an EVP_PKEY * handle for the EVP functions.

一旦将公共密钥加载到RSA *句柄中,就可以使用EVP_PKEY_assign_RSA()将其放入EVP_PKEY *句柄中,以处理EVP函数。

Once you've got this going, to solve the authentication problem I mentioned in my comment, you'll need to established a trusted authority ("Trent"). Trent's public key is known to all users (distributed with the application or similar - just load it from a PEM file). Instead of exchanging bare RSA parameters, Alice and Bob exchange x509 certificates that contain their RSA public keys together with their name, and are signed by Trent. Alice and Bob then each verify the certificate they recieved from the other (using Trent's public key, which they already know), including checking that the associated name is the right one, before continuing the protocol. OpenSSL includes functions for loading and verifying certificates in the x509.h header.

一旦您解决了这个问题,为了解决我在评论中提到的身份验证问题,您需要建立一个可信的权威(“Trent”)。所有用户都知道Trent的公钥(通过应用程序或类似的方式分发),只需从PEM文件中加载它即可。Alice和Bob交换x509证书,而不是交换光的RSA参数,这些证书包含他们的RSA公钥和他们的名字,并由Trent签名。然后,Alice和Bob分别验证他们收到的证书(使用Trent的公钥,他们已经知道),包括在继续协议之前检查关联的名称是否正确。OpenSSL包括在x509中装载和验证证书的功能。h头。


Here's an example of how to use EVP_Seal*() to encrypt a file given the recipient's public key. It takes the PEM RSA Public Key file (ie as generated by openssl rsa -pubout) as a command line argument, reads the source data from stdin and writes the encrypted data to stdout. To decrypt, use EVP_Open*() instead, and PEM_read_RSAPrivateKey() to read a private key rather than public key.

下面是一个如何使用EVP_Seal*()来加密给定接收者的公钥的文件的示例。它将PEM RSA公钥文件(由openssl RSA -pubout生成)作为命令行参数,从stdin读取源数据并将加密数据写入stdout。为了解密,可以使用EVP_Open*()和PEM_read_RSAPrivateKey()来读取私钥,而不是公钥。

It's not really that hard - and certainly less error prone than messing about generating padding, IVs and so on yourself (the Seal function does both the RSA and AES parts of the deal). Anyway, the code:

这并不是真的那么难——当然也不容易出错,而不是在生成填充、IVs等等方面的问题(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;
}

The code you've posted illustrates nicely why you should use the higher-level functions - you've fallen into a couple of pitfalls:

您发布的代码很好地说明了为什么要使用高级功能——您已经陷入了几个陷阱:

  • rand() is emphatically not a cryptographically strong random number generator! Generating your symmetric key using rand() is enough to make the entire system completely insecure. (The EVP_*() functions generate the necessary random numbers themselves, using a cryptographically strong RNG, seeded from an appropriate entropy source).

    rand()绝对不是一个密码强大的随机数生成器!使用rand()生成对称密钥就足以使整个系统完全不安全。(EVP_*()函数本身生成必要的随机数,使用加密强大的RNG,从适当的熵源中生成)。

  • You are setting the IV for CFB mode to a fixed value (zero). This negates any advantage of using CFB mode in the first place (allowing attackers to trivially perform block-replacement attacks and worse). (The EVP_*() functions generate an appropriate IV for you, when required).

    您将CFB模式的IV设置为一个固定值(零)。这就否定了首先使用CFB模式的任何好处(允许攻击者执行拦截攻击和更坏的攻击)。(EVP_*()函数在需要时为您生成适当的IV)。

  • RSA_PKCS1_OAEP_PADDING should be used if you're defining a new protocol, rather than interoperating with an existing protocol.

    如果您正在定义一个新的协议,而不是与现有的协议互操作,那么应该使用RSA_PKCS1_OAEP_PADDING。


The corresponding decryption code, for posterity:

对应的解密代码,为后代:

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

#2


0  

Actually, no problem, I have just read that basically, the RSA object is a structure that contains both public and private fields. One can extract the public field data and only send that to Bob.

实际上,没有问题,我刚读到,RSA对象是一个包含公共和私有字段的结构。可以提取公共字段数据,只将其发送给Bob。

I.e. basically, to extract the public fields from rsa and store each in two different buffers (which are char arrays and can then be sent to Bob), you do:

也就是说,从rsa中提取公共字段并将它们存储在两个不同的缓冲区中(它们是char数组,然后可以发送给Bob),您可以:

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

And then Bob, on the receiving end, reconstructs as follows:

然后Bob,在接收端,重构如下:

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

Bob can then use rsa* to publicly encrypt the symmetric cypher key which can then be sent to Alice. Alice can then decrypt with the private key

然后Bob可以使用rsa*公开加密对称的cypher密钥,然后将其发送给Alice。然后,Alice可以用私钥解密。

Ben.

本。

#3


0  

I write two examples around CAF's code. They are heavily modifed and uses OpenSSL's BIO container for more abstraction.

我在CAF的代码周围写了两个例子。它们被大量修改,并使用OpenSSL的BIO容器进行更多的抽象。

One example uses a file as input and the other example uses a string buffer. It uses RSA and DES, however you can easily change it from the code. Compile instructions are inside the code. I needed a working example, I hope someone find this useful. I also commented the code. You can get it from here:

一个示例使用一个文件作为输入,另一个示例使用一个字符串缓冲区。它使用RSA和DES,但是你可以很容易地从代码中更改它。编译指令在代码内部。我需要一个工作的例子,希望有人觉得这有用。我还注释了代码。你可以从这里得到:

Take file as input: https://github.com/farslan/snippets/blob/master/hybrid_file.c

以文件作为输入:https://github.com/farslan/snippets/blob/master/mixd_file.c。

Take string buffer as input: https://github.com/farslan/snippets/blob/master/hybrid_data.c

将字符串缓冲区作为输入:https://github.com/farslan/snippets/blob/master/mixd_data .c。

#4


0  

Thanks @Caf. Your post helped. However I got

谢谢@Caf。你的帮助。但是我有

The program '[7056] Encryption2.exe: Native' has exited with code -1073741811 (0xc000000d) for the line

程序的[7056]Encryption2。exe: Native已经退出了代码-1073741811 (0xc000000d)。

  PEM_read_RSA_PUBKEY(rsa_pkey_file, &rsa_pkey, NULL, NULL)

I changed to

我改变了

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

Where data has the PEM file with only public key. My challenge was to encrypt in C++ and decrypt in java. I transmitted the base64 encoded ek of size eklen (i did not use eklen_n) and decrypted to get the AES key using the RSA private key. Then I decrypted the cipher file using this AES key. It worked fine.

其中数据只有公钥的PEM文件。我的挑战是用c++加密并在java中解密。我传输了base64编码的eklen大小的ek(我没有使用eklen_n)和解密,以使用RSA私钥获取AES密钥。然后使用这个AES键解密密码文件。它工作得很好。