[英]Can you help me get my head around openssl public key encryption with rsa.h in c++?
我试图在C ++中使用rsa的openssl实现来解决公钥加密问题。 你能帮我吗? 到目前为止,这些是我的想法(请在必要时更正)
现在,我已经查看了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);
如果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);
其次,* rsa公钥应该以什么格式发送给Bob? 是否必须将其重新解释为字符数组然后以标准方式发送? 我听说过证书 - 它们与它有什么关系吗?
抱歉,所有的问题,祝福,本。
编辑: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(){}
您实际上应该使用openssl/evp.h
的更高级别的“Envelope Encryption”函数,而不是直接使用低级RSA函数。 这些为您完成大部分工作,并且意味着您不必重新发明轮子。
在这种情况下,您将使用EVP_SealInit()
, EVP_SealUpdate()
和EVP_SealFinal()
函数。 相应的解密函数是EVP_OpenInit()
, EVP_OpenUpdate()
和EVP_OpenFinal()
。 我建议使用EVP_aes_128_cbc()
作为密码类型参数的值。
将公钥加载到RSA *
句柄后,可以使用EVP_PKEY_assign_RSA()
将其放入EVP函数的EVP_PKEY *
句柄中。
一旦你完成了这个,为了解决我在评论中提到的身份验证问题,你需要建立一个可信任的权限(“Trent”)。 Trent的公钥是所有用户都知道的(与应用程序或类似的一起分发 - 只需从PEM文件加载它)。 Alice和Bob不再交换裸RSA参数,而是交换包含其RSA公钥及其名称的x509证书,并由Trent签名。 然后,Alice和Bob各自验证他们从另一方获得的证书(使用Trent的公钥,他们已经知道),包括在继续协议之前检查相关名称是否正确。 OpenSSL包括用于加载和验证x509.h
标头中的证书的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,从适当的熵源播种)。
您正在将CFB模式的IV设置为固定值(零)。 这首先否定了使用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;
}
我围绕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
谢谢@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解密。 我传输了大小为eklen的base64编码ek(我没有使用eklen_n)并使用RSA私钥解密以获取AES密钥。 然后我使用此AES密钥解密密码文件。 它工作正常。
实际上,没问题,我刚刚读到,基本上,RSA对象是一个包含公共和私有字段的结构。 可以提取公共字段数据并仅将其发送给Bob。
即基本上,要从rsa中提取公共字段并将它们存储在两个不同的缓冲区(可以是char数组,然后可以发送给Bob)中,您可以:
sprintf(keyBufferA,"%s\r\n",BN_bn2hex(rsa->n));
sprintf(keyBufferB,"%s\r\n",BN_bn2hex(rsa->e));
然后Bob在接收端重建如下:
rsa = RSA_new();
BN_hex2bn(&rsa->n, keybufA);
BN_hex2bn(&rsa->e, keybufB);
然后Bob可以使用rsa *公开加密对称密码密钥,然后可以将其发送给Alice。 然后Alice可以使用私钥解密
本。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.