【问题标题】:Decrypting an AES-encrypted message with an RSA-encrypted key with EVP tool使用 EVP 工具使用 RSA 加密密钥解密 AES 加密消息
【发布时间】: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.htmlopenssl.org/docs/man1.1.0/crypto/ERR_get_error.htmlEVP_OpenUpdate not 是否需要多次调用 - 一次就足够了。如果加密数据正在流式传输给您,您可以选择多次调用它。
  • 是的,只要密钥已使用带有 PKCS#1 填充的 RSA 加密,就可以使用它调用 OpenERR_print_errors_fp 是一个简单的调用,只需将所有错误转储到(比如说)stderr。有些人希望更好地控制错误消息的格式及其显示方式(例如,您可能不想将其发送到 FILE *,而是发送到一些自定义日志记录 API)。如果您想获得更多控制权,您可以使用ERR_get_error,然后使用其他各种ERR_* 函数来获取组件并按照您的意愿进行操作。

标签: c openssl aes rsa libcrypto


【解决方案1】:

这是一个完整的工作示例,说明如何使用 RSA 加密密钥,并使用 AES 使用该密钥加密消息,然后对这些内容进行后续解密。它假定正在使用 AES-256-CBC。如果您想改用 AES-256-GCM,则需要进行一些更改以获取和设置标签(询问我是否需要有关如何执行此操作的一些指示)。它还假设 RSA 加密是使用 PKCS#1 填充完成的(这是 EVP_Seal* API 支持的全部内容)。如果您需要其他类型的填充,那么您将需要使用不同的方法。最后,它假设您使用的是 OpenSSL 1.1.0。如果您使用的是 1.0.2,则可能需要进行一些更改(至少您需要显式初始化和取消初始化库 - 这在 1.1.0 中不是必需的)。

代码从当前工作目录中名为 privkey.pem 和 pubkey.pem 的文件中读取 RSA 私钥和公钥。我像这样生成了这些文件:

openssl genrsa -out privkey.pem 2048
openssl rsa -in privkey.pem -pubout -out pubkey.pem

我仅在 Linux 上对此进行了测试。代码如下:

#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int envelope_seal(EVP_PKEY *pub_key, unsigned char *plaintext,
                         int plaintext_len, unsigned char **encrypted_key,
                         int *encrypted_key_len, unsigned char **iv,
                         int *iv_len,  unsigned char **ciphertext,
                         int *ciphertext_len)
{
    EVP_CIPHER_CTX *ctx;
    int len, ret = 0;
    const EVP_CIPHER *type = EVP_aes_256_cbc();
    unsigned char *tmpiv = NULL, *tmpenc_key = NULL, *tmpctxt = NULL;

    if((ctx = EVP_CIPHER_CTX_new()) == NULL)
        return 0;

    *iv_len = EVP_CIPHER_iv_length(type);
    if ((tmpiv = malloc(*iv_len)) == NULL)
        goto err;

    if ((tmpenc_key = malloc(EVP_PKEY_size(pub_key))) == NULL)
        goto err;

    if ((tmpctxt = malloc(plaintext_len + EVP_CIPHER_block_size(type)))
            == NULL)
        goto err;

    if(EVP_SealInit(ctx, type, &tmpenc_key, encrypted_key_len, tmpiv, &pub_key,
                    1) != 1)
        goto err;

    if(EVP_SealUpdate(ctx, tmpctxt, &len, plaintext, plaintext_len) != 1)
        goto err;
    *ciphertext_len = len;

    if(EVP_SealFinal(ctx, tmpctxt + len, &len) != 1)
        goto err;
    *ciphertext_len += len;

    *iv = tmpiv;
    *encrypted_key = tmpenc_key;
    *ciphertext = tmpctxt;
    tmpiv = NULL;
    tmpenc_key = NULL;
    tmpctxt = NULL;
    ret = 1;
 err:
    EVP_CIPHER_CTX_free(ctx);
    free(tmpiv);
    free(tmpenc_key);
    free(tmpctxt);

    return ret;
}

int envelope_open(EVP_PKEY *priv_key, unsigned char *ciphertext,
                  int ciphertext_len, unsigned char *encrypted_key,
                  int encrypted_key_len, unsigned char *iv,
                  unsigned char **plaintext, int *plaintext_len)
{
    EVP_CIPHER_CTX *ctx;
    int len, ret = 0;
    unsigned char *tmpptxt = NULL;

    if((ctx = EVP_CIPHER_CTX_new()) == NULL)
        return 0;

    if ((tmpptxt = malloc(ciphertext_len)) == NULL)
        goto err;

    if(EVP_OpenInit(ctx, EVP_aes_256_cbc(), encrypted_key, encrypted_key_len,
                    iv, priv_key) != 1)
        return 0;

    if(EVP_OpenUpdate(ctx, tmpptxt, &len, ciphertext, ciphertext_len) != 1)
        return 0;
    *plaintext_len = len;

    if(EVP_OpenFinal(ctx, tmpptxt + len, &len) != 1)
        return 0;
    *plaintext_len += len;

    *plaintext = tmpptxt;
    tmpptxt = NULL;
    ret = 1;
 err:
    EVP_CIPHER_CTX_free(ctx);
    free(tmpptxt);

    return ret;
}

int main(void)
{
    EVP_PKEY *pubkey = NULL, *privkey = NULL;
    FILE *pubkeyfile, *privkeyfile;
    int ret = 1;
    unsigned char *iv = NULL, *message = "Hello World!\n";
    unsigned char *enc_key = NULL, *ciphertext = NULL, *plaintext = NULL;
    int iv_len = 0, enc_key_len = 0, ciphertext_len = 0, plaintext_len = 0, i;

    if ((pubkeyfile = fopen("pubkey.pem", "r")) == NULL) {
        printf("Failed to open public key for reading\n");
        goto err;
    }
    if ((pubkey = PEM_read_PUBKEY(pubkeyfile, &pubkey, NULL, NULL)) == NULL) {
        fclose(pubkeyfile);
        goto err;
    }
    fclose(pubkeyfile);

    if ((privkeyfile = fopen("privkey.pem", "r")) == NULL) {
        printf("Failed to open private key for reading\n");
        goto err;
    }
    if ((privkey = PEM_read_PrivateKey(privkeyfile, &privkey, NULL, NULL))
            == NULL) {
        fclose(privkeyfile);
        goto err;
    }
    fclose(privkeyfile);

    if (!envelope_seal(pubkey, message, strlen(message), &enc_key, &enc_key_len,
                       &iv, &iv_len, &ciphertext, &ciphertext_len))
        goto err;

    printf("Ciphertext:\n");
    for (i = 0; i < ciphertext_len; i++)
        printf("%02x", ciphertext[i]);
    printf("\n");

    printf("Encrypted Key:\n");
    for (i = 0; i < enc_key_len; i++)
        printf("%02x", enc_key[i]);
    printf("\n");

    printf("IV:\n");
    for (i = 0; i < iv_len; i++)
        printf("%02x", iv[i]);
    printf("\n");

    if (!envelope_open(privkey, ciphertext, ciphertext_len, enc_key,
                       enc_key_len, iv, &plaintext, &plaintext_len))
        goto err;

    plaintext[plaintext_len] = '\0';
    printf("Plaintext: %s\n", plaintext);

    ret = 0;
 err:
    if (ret != 0) {
        printf("Error\n");
        ERR_print_errors_fp(stdout);
    }
    EVP_PKEY_free(pubkey);
    EVP_PKEY_free(privkey);
    free(iv);
    free(enc_key);
    free(ciphertext);
    free(plaintext);

    return ret;
}

【讨论】:

    【解决方案2】:

    您的密钥已使用 RSA 加密,因此您将首先使用 RSA_private_decrypt 等 RSA API 对其进行解密,而不是使用 EVP* API。

    一旦您获得密钥解密,您需要将其与 (EVP*) API 一起使用,以通过 AES 解密数据。

    【讨论】:

    • 我应该使用什么模式?使用 RSA 解密的 AES 密钥后,我仍然遇到同样的错误
    • 您需要将正确的密码类型传递给 EVP_DecryptInit_ex,根据使用的密钥长度,您可以使用 EVP_aes_128_cbc,EVP_aes_192_cbc,EVP_aes_256_cbc
    • IV 不是使用 cbc 模式吗?
    • 密码类型应该与加密期间使用的密码类型相同,您可以在 evp_decryptinit_ex 的手册页中找到密码类型列表,当您提到没有使用 IV 时,我相信您的意思是全零作为 IV ,它在 EVP_DecryptUpdatetoo 中还是在 EVP_DecryptFinal_ex 中失败了?
    • EVP_DecryptUpdate 解密不是密码块倍数的最终块,如果数据已经是密码块的倍数,我不确定它是否返回 1,你能检查你从 EVP_DecryptUpdate 获得的数据是否是解密您期望的纯数据,请注意,您必须在 iv、加密模式、密钥上与客户端(加密的人)同步,即在解密时使用相同的密钥以获得正确的解密数据。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-01
    • 2011-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多