【发布时间】:2017-11-08 12:56:09
【问题描述】:
出于工业目的,我想用 C 中的 RSA 加密密钥解密 AES 加密的消息。起初,我想先一步一步地使用 OpenSSL libcrypto 库,首先 RSA 解码密钥,然后 AES 解码数据。
我发现 EVP 工具通常被认为是执行此操作的更好方法,因为它实际上执行了低级函数所做的但正确的操作。 这是我看到的程序流程:
- 初始化 OpenSSL;
- 读取并存储 RSA 私钥;
- 通过指定解密算法 (AES) 和私钥来初始化解密;
- 通过提供密钥、数据、密钥及其长度来更新解密
- 最后解密数据并返回。
到目前为止,我们不打算使用任何 IV 或 ADD(尽管 IV 可能会在项目的后期出现),这让我感到很困惑。我关注了this guide,不是很清楚,不适合我使用EVP的方式。
这是我的实际代码:
#include <openssl/evp.h>
#include <openssl/conf.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/aes.h>
#include <openssl/err.h>
#include "openssl\applink.c"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
const char PRIVATE_KEY_PATH[] = "C:/Users/Local_user/privateKey.pem";
EVP_PKEY* initializePrivateKey(void)
{
FILE* privateKeyfile;
if ((privateKeyfile = fopen(PRIVATE_KEY_PATH, "r")) == NULL) // Check PEM file opening
{
perror("Error while trying to access to private key.\n");
return NULL;
}
RSA *rsaPrivateKey = RSA_new();
EVP_PKEY *privateKey = EVP_PKEY_new();
if ((rsaPrivateKey = PEM_read_RSAPrivateKey(privateKeyfile, &rsaPrivateKey, NULL, NULL)) == NULL) // Check PEM file reading
{
fprintf(stderr, "Error loading RSA Private Key File.\n");
ERR_print_errors_fp(stderr);
return NULL;
}
if (!EVP_PKEY_assign_RSA(privateKey, rsaPrivateKey))
{
fprintf(stderr, "Error when initializing EVP private key.\n");
ERR_print_errors_fp(stderr);
return NULL;
}
return privateKey;
}
const uint8_t* decodeWrappingKey(uint8_t const* data, const size_t data_len, uint8_t const* wrappingKey, const size_t wrappingKey_len)
{
// Start Decryption
EVP_CIPHER_CTX *ctx;
if (!(ctx = EVP_CIPHER_CTX_new())) // Initialize context
{
fprintf(stderr, "Error when initializing context.\n");
ERR_print_errors_fp(stderr);
return NULL;
}
EVP_PKEY *privateKey = initializePrivateKey();
if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, privateKey, NULL)) // Initialize decryption
{
fprintf(stderr, "Error when initializing decryption.\n");
ERR_print_errors_fp(stderr);
return NULL;
}
uint8_t* res;
if ((res = calloc(data_len, sizeof(uint8_t))) == NULL) // Check memory allocating
{
perror("Memory allocating error ");
return NULL;
}
puts("Initialization done. Decoding..\n");
size_t res_len = 0;
if (1 != EVP_DecryptUpdate(ctx, res, &res_len, data, data_len))
{
fprintf(stderr, "Error when preparing decryption.\n");
ERR_print_errors_fp(stderr);
}
if (1 != EVP_DecryptFinal_ex(ctx, res, &res_len))
{
fprintf(stderr, "Error when decrypting.\n");
ERR_print_errors_fp(stderr);
}
return res;
}
void hexToBytes(uint8_t *des, char const *source, const size_t size) {
for (int i = 0; i < size - 1; i += 2)
sscanf(source + i, "%02x", des + (i / 2));
}
int main(void) {
char const *strWrap = "5f82c48f85054ef6a3b2621819dd0e969030c79cc00deb89........";
char const *strData = "ca1518d44716e3a4588af741982f29ad0a3e7a8d67.....";
uint8_t *wrap = calloc(strlen(strWrap), sizeof(uint8_t));
hexToBytes(wrap, strWrap, strlen(strWrap)); // Converts string to raw data
uint8_t *data = calloc(strlen(strData), sizeof(uint8_t));
hexToBytes(data, strData, strlen(strData));
/* Load the human readable error strings for libcrypto */
ERR_load_crypto_strings();
/* Load all digest and cipher algorithms */
OpenSSL_add_all_algorithms();
/* Load config file, and other important initialisation */
OPENSSL_config(NULL);
const uint8_t *res = decodeWrappingKey(data, strlen(strData) / 2, wrap, strlen(strWrap) / 2);
if (res == NULL)
return 1;
return 0;
}
我的输出如下:
Initialization done. Decoding..
Error when preparing decryption.
Error when decrypting.
很明显,在更新和完成解密时它失败了,但我不知道为什么,到目前为止一直对我有用的 ERR_print_errors_fp(stderr); 似乎是静音的。
【问题讨论】:
-
您能否在edit 中包含一个说明您的问题的最小的、独立的、可编译的示例?我很确定您包含的代码具有该代码中不包含的先决条件;当然只是将它提供给 GCC 不会编译(我得到 28 行诊断输出,包括“在此函数中首次使用”的几行)。
-
您的密钥使用 RSA 加密,因此您将使用 RSA_private_decrypt 等 RSA API 对其进行解密,而不是使用 EVP* api,您将使用它们 (EVP*) 来处理使用 AES 加密的数据
-
您是否考虑过使用 EVP_Seal API?它们提供了一个高级界面,可以完全按照您的描述进行操作,并隐藏所有低级细节。见openssl.org/docs/man1.1.0/crypto/EVP_SealInit.html
-
wiki.openssl.org/index.php/… 提供了一些示例代码。至于错误,如果函数返回错误代码,您可以使用各种错误函数获得更详细的错误消息,例如见openssl.org/docs/man1.1.0/crypto/ERR_print_errors_fp.html 或openssl.org/docs/man1.1.0/crypto/ERR_get_error.html。
EVP_OpenUpdatenot 是否需要多次调用 - 一次就足够了。如果加密数据正在流式传输给您,您可以选择多次调用它。 -
是的,只要密钥已使用带有 PKCS#1 填充的 RSA 加密,就可以使用它调用
Open。ERR_print_errors_fp是一个简单的调用,只需将所有错误转储到(比如说)stderr。有些人希望更好地控制错误消息的格式及其显示方式(例如,您可能不想将其发送到 FILE *,而是发送到一些自定义日志记录 API)。如果您想获得更多控制权,您可以使用ERR_get_error,然后使用其他各种ERR_*函数来获取组件并按照您的意愿进行操作。
标签: c openssl aes rsa libcrypto