c - 使用 EVP 工具使用 RSA 加密 key 解密 AES 加密消息

标签 c openssl aes rsa libcrypto

出于工业目的,我想用 C 语言解密使用 RSA 加密 key 的 AES 加密消息。起初,我想先一步一步地进行,使用 OpenSSL libcrypto 库,首先对 key 进行 RSA 解码,然后对数据进行 AES 解码。

我发现 EVP 工具通常被视为实现此目的的更好方法,因为它实际上执行低级函数的操作,但正确无误。 这是我看到的程序流程:

  • 初始化 OpenSSL;
  • 读取并存储 RSA 私钥;
  • 通过指定解密算法 (AES) 和私钥来初始化解密;
  • 通过提供 key 、数据、 key 及其长度来更新解密
  • 最终解密数据并返回。

到目前为止,我们不打算使用任何 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); 似乎是静音的。

最佳答案

这里是一个完整的工作示例,说明如何使用 RSA 加密 key ,并使用该 key 使用 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;
}

关于c - 使用 EVP 工具使用 RSA 加密 key 解密 AES 加密消息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44408110/

相关文章:

c - 如果 char[] 位于 struct __attribute__((aligned)) 内部,则 char[] 的步长是否保证为 1?

java - 在 O(n) 中查找数组中的所有差异

c++ - 使用 AES_256_CBC 解密文件返回 "bad decrypt"错误

c - 如何使用 MPI 在 C 中修复 AES 解密

在 C 中的嵌套循环内继续

c - 为什么 exevp 之前的 printf 没有运行?

php - 使用 Blowfish 和 ECB 将 mcrypt 迁移到 OpenSSL

openssl - 如何使用 NSS 或 GnuTLS 而不是 OpenSSL 构建 openSSH?

ssl - Openssl 使用链式 CA 和链式证书进行验证

java - 在代码中解密 AES 加密的授权 token 方法时 Jar 崩溃。 hs_err _pid 文件提到 arrayof_jbyte_fill